agora inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH v51 01/10] Make index_concurrently_create_copy more general
374+ messages / 2 participants
[nested] [flat]

* [PATCH v51 01/10] Make index_concurrently_create_copy more general
@ 2026-03-24 18:02 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Álvaro Herrera @ 2026-03-24 18:02 UTC (permalink / raw)

Add a 'boolean concurrent' option, and make it work for both cases.
Also rename it to index_create_copy.

This allows it to be reused for other purposes -- specifically, for
REPACK CONCURRENTLY.

With the CONCURRENTLY option, REPACK cannot simply swap the heap file and
rebuild its indexes. Instead, it needs to build a separate set of indexes
(including system catalog entries) *before* the actual swap, to reduce the
time AccessExclusiveLock needs to be held for.  This approach is
different from what CREATE INDEX CONCURRENTLY does.

Per a suggestion from Mihail Nikalayeu.

Author: Antonin Houska <[email protected]>
Discussion: https://postgr.es/m/41104.1754922120@localhost
---
 src/backend/catalog/index.c      | 41 ++++++++++++++++++++++----------
 src/backend/commands/indexcmds.c |  9 +++----
 src/include/catalog/index.h      |  7 +++---
 3 files changed, 36 insertions(+), 21 deletions(-)

diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 1ccfa687f05..e418d67e8e4 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1289,17 +1289,17 @@ index_create(Relation heapRelation,
 }
 
 /*
- * index_concurrently_create_copy
+ * index_create_copy
  *
- * Create concurrently an index based on the definition of the one provided by
- * caller.  The index is inserted into catalogs and needs to be built later
- * on.  This is called during concurrent reindex processing.
+ * Create an index based on the definition of the one provided by caller.  The
+ * index is inserted into catalogs. If 'concurrently' is TRUE, it needs to be
+ * built later on; otherwise it's built immediately.
  *
  * "tablespaceOid" is the tablespace to use for this index.
  */
 Oid
-index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
-							   Oid tablespaceOid, const char *newName)
+index_create_copy(Relation heapRelation, bool concurrently,
+				  Oid oldIndexId, Oid tablespaceOid, const char *newName)
 {
 	Relation	indexRelation;
 	IndexInfo  *oldInfo,
@@ -1318,6 +1318,7 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
 	List	   *indexColNames = NIL;
 	List	   *indexExprs = NIL;
 	List	   *indexPreds = NIL;
+	int			flags = 0;
 
 	indexRelation = index_open(oldIndexId, RowExclusiveLock);
 
@@ -1328,7 +1329,7 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
 	 * Concurrent build of an index with exclusion constraints is not
 	 * supported.
 	 */
-	if (oldInfo->ii_ExclusionOps != NULL)
+	if (oldInfo->ii_ExclusionOps != NULL && concurrently)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("concurrent index creation for exclusion constraints is not supported")));
@@ -1384,9 +1385,7 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
 	}
 
 	/*
-	 * Build the index information for the new index.  Note that rebuild of
-	 * indexes with exclusion constraints is not supported, hence there is no
-	 * need to fill all the ii_Exclusion* fields.
+	 * Build the index information for the new index.
 	 */
 	newInfo = makeIndexInfo(oldInfo->ii_NumIndexAttrs,
 							oldInfo->ii_NumIndexKeyAttrs,
@@ -1395,11 +1394,24 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
 							indexPreds,
 							oldInfo->ii_Unique,
 							oldInfo->ii_NullsNotDistinct,
-							false,	/* not ready for inserts */
-							true,
+							!concurrently,	/* isready */
+							concurrently,	/* concurrent */
 							indexRelation->rd_indam->amsummarizing,
 							oldInfo->ii_WithoutOverlaps);
 
+	/* fetch exclusion constraint info if any */
+	if (indexRelation->rd_index->indisexclusion)
+	{
+		/*
+		 * XXX Beware: we're making newInfo point to oldInfo-owned memory.  It
+		 * would be more orthodox to palloc+memcpy, but we don't need that
+		 * here at present.
+		 */
+		newInfo->ii_ExclusionOps = oldInfo->ii_ExclusionOps;
+		newInfo->ii_ExclusionProcs = oldInfo->ii_ExclusionProcs;
+		newInfo->ii_ExclusionStrats = oldInfo->ii_ExclusionStrats;
+	}
+
 	/*
 	 * Extract the list of column names and the column numbers for the new
 	 * index information.  All this information will be used for the index
@@ -1436,6 +1448,9 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
 		stattargets[i].isnull = isnull;
 	}
 
+	if (concurrently)
+		flags = INDEX_CREATE_SKIP_BUILD | INDEX_CREATE_CONCURRENT;
+
 	/*
 	 * Now create the new index.
 	 *
@@ -1459,7 +1474,7 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
 							  indcoloptions->values,
 							  stattargets,
 							  reloptionsDatum,
-							  INDEX_CREATE_SKIP_BUILD | INDEX_CREATE_CONCURRENT,
+							  flags,
 							  0,
 							  true, /* allow table to be a system catalog? */
 							  false,	/* is_internal? */
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 373e8234794..cba379810c7 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -3989,10 +3989,11 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 			tablespaceid = indexRel->rd_rel->reltablespace;
 
 		/* Create new index definition based on given index */
-		newIndexId = index_concurrently_create_copy(heapRel,
-													idx->indexId,
-													tablespaceid,
-													concurrentName);
+		newIndexId = index_create_copy(heapRel,
+									   true,
+									   idx->indexId,
+									   tablespaceid,
+									   concurrentName);
 
 		/*
 		 * Now open the relation of the new index, a session-level lock is
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index a38e95bc0eb..ed9e4c37d27 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -101,10 +101,9 @@ extern Oid	index_create(Relation heapRelation,
 #define	INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS	(1 << 4)
 #define	INDEX_CONSTR_CREATE_WITHOUT_OVERLAPS (1 << 5)
 
-extern Oid	index_concurrently_create_copy(Relation heapRelation,
-										   Oid oldIndexId,
-										   Oid tablespaceOid,
-										   const char *newName);
+extern Oid	index_create_copy(Relation heapRelation, bool concurrently,
+							  Oid oldIndexId, Oid tablespaceOid,
+							  const char *newName);
 
 extern void index_concurrently_build(Oid heapRelationId,
 									 Oid indexRelationId);
-- 
2.47.3


--r2slln3zpmilwu22
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v51-0002-Rename-cluster.c-h-repack.c-h.patch"



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

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

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

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


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



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

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

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 127d4415084..2866a5e696e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index ec100e3eef5..fa98b8b9247 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread

* [PATCH 2/8] Move functions to repack.c.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 374+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

In the next patches, the functions will be called from other modules. Use a
separate diff for the move so that the following diffs are a bit easier to
read.
---
 src/backend/access/heap/heapam_handler.c | 97 +-----------------------
 src/backend/commands/repack.c            | 91 ++++++++++++++++++++++
 src/include/commands/repack.h            |  7 ++
 3 files changed, 99 insertions(+), 96 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4db8a16f691..97f850ebf73 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -34,6 +34,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
+#include "commands/repack.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -50,11 +51,6 @@
 
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
-static void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
-static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
-static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 		heap_freetuple(newtuple);
 }
 
-/*
- * Insert tuple when processing REPACK CONCURRENTLY.
- *
- * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
- *
- * 'reform' is a slot to use for tuple "reforming", typically to get set
- * values of dropped columns to NULL.
- *
- * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
- * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
- * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
- */
-static void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
-{
-	HeapTuple	tuple;
-	bool		shouldFree;
-	TupleTableSlot *slot;
-
-	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
-	{
-		clear_dropped_attributes(tuple, reform);
-		slot = reform;
-	}
-	else
-		slot = src;
-
-	/*
-	 * clear_dropped_attributes() should have deformed the tuple, so nothing
-	 * should depend on it now.
-	 */
-	if (shouldFree)
-		heap_freetuple(tuple);
-
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
-}
-
-static bool
-tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
-{
-	/*
-	 * A short tuple might require values from attmissing val, so activate the
-	 * coding unconditionally in that case.  The value might legitimally be
-	 * NULL otherwise, so this is slightly wasteful, but it probably beats
-	 * having to test each attribute for presence of attmissingval each time.
-	 */
-	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
-		return true;
-
-	/* Does it have dropped attributes? */
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
-			!heap_attisnull(tuple, i + 1, tupDesc))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
- *
- * Set values of dropped columns to NULL,
- */
-static void
-clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
-{
-	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
-
-	/* Assuming 'reform' is virtual, this deforms the tuple. */
-	Assert(TTS_IS_VIRTUAL(reform));
-	ExecForceStoreHeapTuple(tuple, reform, false);
-
-	for (int i = 0; i < tupDesc->natts; i++)
-	{
-		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
-			reform->tts_isnull[i] = true;
-	}
-}
-
 /*
  * Check visibility of the tuple.
  */
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 02883fe34a4..19927c15bf3 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 	return OIDNewHeap;
 }
 
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * 'reform' is a slot to use for tuple "reforming", typically to get set
+ * values of dropped columns to NULL.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+void
+heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+{
+	HeapTuple	tuple;
+	bool		shouldFree;
+	TupleTableSlot *slot;
+
+	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
+	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	{
+		clear_dropped_attributes(tuple, reform);
+		slot = reform;
+	}
+	else
+		slot = src;
+
+	/*
+	 * clear_dropped_attributes() should have deformed the tuple, so nothing
+	 * should depend on it now.
+	 */
+	if (shouldFree)
+		heap_freetuple(tuple);
+
+	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, bistate);
+}
+
+bool
+tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc)
+{
+	/*
+	 * A short tuple might require values from attmissing val, so activate the
+	 * coding unconditionally in that case.  The value might legitimally be
+	 * NULL otherwise, so this is slightly wasteful, but it probably beats
+	 * having to test each attribute for presence of attmissingval each time.
+	 */
+	if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts)
+		return true;
+
+	/* Does it have dropped attributes? */
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped &&
+			!heap_attisnull(tuple, i + 1, tupDesc))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack.
+ *
+ * Set values of dropped columns to NULL,
+ */
+void
+clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
+{
+	TupleDesc	tupDesc = reform->tts_tupleDescriptor;
+
+	/* Assuming 'reform' is virtual, this deforms the tuple. */
+	Assert(TTS_IS_VIRTUAL(reform));
+	ExecForceStoreHeapTuple(tuple, reform, false);
+
+	for (int i = 0; i < tupDesc->natts; i++)
+	{
+		if (TupleDescCompactAttr(tupDesc, i)->attisdropped)
+			reform->tts_isnull[i] = true;
+	}
+}
+
 /*
  * Do the physical copying of table data.
  *
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..27105c10591 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -15,6 +15,8 @@
 
 #include <signal.h>
 
+#include "access/hio.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
+extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
+								   TupleTableSlot *reform,
+								   BulkInsertStateData *bistate);
+extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
+extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 bool is_system_catalog,
 							 bool swap_toast_by_content,
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0003-Introduce-RepackDest-structure.patch



^ permalink  raw  reply  [nested|flat] 374+ messages in thread


end of thread, other threads:[~2026-07-15 08:37 UTC | newest]

Thread overview: 374+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-03-24 18:02 [PATCH v51 01/10] Make index_concurrently_create_copy more general Álvaro Herrera <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 2/8] Move functions to 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]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox