agora inbox for [email protected]help / color / mirror / Atom feed
[PATCH 3/6] Move conversion of a "historic" to MVCC snapshot to a separate function. 120+ messages / 1 participants [nested] [flat]
* [PATCH 3/6] Move conversion of a "historic" to MVCC snapshot to a separate function. @ 2026-01-27 10:48 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-01-27 10:48 UTC (permalink / raw) The conversion is now handled by SnapBuildMVCCFromHistoric(). REPACK CONCURRENTLY will also need it. --- src/backend/replication/logical/snapbuild.c | 59 +++++++++++++++++---- src/backend/utils/time/snapmgr.c | 3 +- src/include/replication/snapbuild.h | 1 + src/include/utils/snapmgr.h | 1 + 4 files changed, 52 insertions(+), 12 deletions(-) diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index 7f79621b57e..a738ad8a864 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -440,10 +440,7 @@ Snapshot SnapBuildInitialSnapshot(SnapBuild *builder) { Snapshot snap; - TransactionId xid; TransactionId safeXid; - TransactionId *newxip; - int newxcnt = 0; Assert(XactIsoLevel == XACT_REPEATABLE_READ); Assert(builder->building_full_snapshot); @@ -485,7 +482,35 @@ SnapBuildInitialSnapshot(SnapBuild *builder) MyProc->xmin = snap->xmin; - /* allocate in transaction context */ + /* Convert the historic snapshot to MVCC snapshot. */ + return SnapBuildMVCCFromHistoric(snap, true); +} + +/* + * Turn a historic MVCC snapshot into an ordinary MVCC snapshot. + * + * Unlike a regular (non-historic) MVCC snapshot, the 'xip' array of this + * snapshot contains not only running main transactions, but also their + * subtransactions. On the other hand, 'subxip' will usually be empty. This + * difference does not affect the result of XidInMVCCSnapshot() because it + * searches both in 'xip' and 'subxip'. + * + * Pass true for 'in_place' if you don't care about modifying the source + * snapshot. If you need a new instance, and one that was allocated as a + * single chunk of memory, pass false. + */ +Snapshot +SnapBuildMVCCFromHistoric(Snapshot snapshot, bool in_place) +{ + TransactionId xid; + TransactionId *oldxip = snapshot->xip; + uint32 oldxcnt = snapshot->xcnt; + TransactionId *newxip; + int newxcnt = 0; + Snapshot result; + + Assert(snapshot->snapshot_type == SNAPSHOT_HISTORIC_MVCC); + newxip = palloc_array(TransactionId, GetMaxSnapshotXidCount()); /* @@ -494,7 +519,7 @@ SnapBuildInitialSnapshot(SnapBuild *builder) * classical snapshot by marking all non-committed transactions as * in-progress. This can be expensive. */ - for (xid = snap->xmin; NormalTransactionIdPrecedes(xid, snap->xmax);) + for (xid = snapshot->xmin; NormalTransactionIdPrecedes(xid, snapshot->xmax);) { void *test; @@ -502,7 +527,7 @@ SnapBuildInitialSnapshot(SnapBuild *builder) * Check whether transaction committed using the decoding snapshot * meaning of ->xip. */ - test = bsearch(&xid, snap->xip, snap->xcnt, + test = bsearch(&xid, snapshot->xip, snapshot->xcnt, sizeof(TransactionId), xidComparator); if (test == NULL) @@ -519,11 +544,25 @@ SnapBuildInitialSnapshot(SnapBuild *builder) } /* adjust remaining snapshot fields as needed */ - snap->snapshot_type = SNAPSHOT_MVCC; - snap->xcnt = newxcnt; - snap->xip = newxip; + snapshot->xcnt = newxcnt; + snapshot->xip = newxip; + + if (in_place) + result = snapshot; + else + { + result = CopySnapshot(snapshot); + + /* Restore the original values so the source is intact. */ + snapshot->xip = oldxip; + snapshot->xcnt = oldxcnt; + + /* newxip has been copied */ + pfree(newxip); + } + result->snapshot_type = SNAPSHOT_MVCC; - return snap; + return result; } /* diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c index 2e6197f5f35..3af1b366adf 100644 --- a/src/backend/utils/time/snapmgr.c +++ b/src/backend/utils/time/snapmgr.c @@ -213,7 +213,6 @@ typedef struct ExportedSnapshot static List *exportedSnapshots = NIL; /* Prototypes for local functions */ -static Snapshot CopySnapshot(Snapshot snapshot); static void UnregisterSnapshotNoOwner(Snapshot snapshot); static void FreeSnapshot(Snapshot snapshot); static void SnapshotResetXmin(void); @@ -604,7 +603,7 @@ SetTransactionSnapshot(Snapshot sourcesnap, VirtualTransactionId *sourcevxid, * The copy is palloc'd in TopTransactionContext and has initial refcounts set * to 0. The returned snapshot has the copied flag set. */ -static Snapshot +Snapshot CopySnapshot(Snapshot snapshot) { Snapshot newsnap; diff --git a/src/include/replication/snapbuild.h b/src/include/replication/snapbuild.h index ccded021433..34383dea776 100644 --- a/src/include/replication/snapbuild.h +++ b/src/include/replication/snapbuild.h @@ -73,6 +73,7 @@ extern void FreeSnapshotBuilder(SnapBuild *builder); extern void SnapBuildSnapDecRefcount(Snapshot snap); extern Snapshot SnapBuildInitialSnapshot(SnapBuild *builder); +extern Snapshot SnapBuildMVCCFromHistoric(Snapshot snapshot, bool in_place); extern const char *SnapBuildExportSnapshot(SnapBuild *builder); extern void SnapBuildClearExportedSnapshot(void); extern void SnapBuildResetExportedSnapshotState(void); diff --git a/src/include/utils/snapmgr.h b/src/include/utils/snapmgr.h index b8c01a291a1..de824945f0b 100644 --- a/src/include/utils/snapmgr.h +++ b/src/include/utils/snapmgr.h @@ -63,6 +63,7 @@ extern Snapshot GetTransactionSnapshot(void); extern Snapshot GetLatestSnapshot(void); extern void SnapshotSetCommandId(CommandId curcid); +extern Snapshot CopySnapshot(Snapshot snapshot); extern Snapshot GetCatalogSnapshot(Oid relid); extern Snapshot GetNonHistoricCatalogSnapshot(Oid relid); extern void InvalidateCatalogSnapshot(void); -- 2.47.3 --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=v32-0004-Add-CONCURRENTLY-option-to-REPACK-command.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-06-16 11:54 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 127d4415084..2866a5e696e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2382,97 +2378,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index ec100e3eef5..fa98b8b9247 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1268,6 +1268,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v01-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
* [PATCH 2/8] Move functions to repack.c. @ 2026-07-15 08:37 Antonin Houska <[email protected]> 0 siblings, 0 replies; 120+ messages in thread From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw) In the next patches, the functions will be called from other modules. Use a separate diff for the move so that the following diffs are a bit easier to read. --- src/backend/access/heap/heapam_handler.c | 97 +----------------------- src/backend/commands/repack.c | 91 ++++++++++++++++++++++ src/include/commands/repack.h | 7 ++ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 4db8a16f691..97f850ebf73 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -34,6 +34,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "commands/progress.h" +#include "commands/repack.h" #include "executor/executor.h" #include "miscadmin.h" #include "pgstat.h" @@ -50,11 +51,6 @@ static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, RewriteState rwstate); -static void heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, - BulkInsertStateData *bistate); -static bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); -static void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -2376,97 +2372,6 @@ reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, heap_freetuple(newtuple); } -/* - * Insert tuple when processing REPACK CONCURRENTLY. - * - * rewriteheap.c is not used in the CONCURRENTLY case because it'd be - * difficult to do the same in the catch-up phase (as the logical - * decoding does not provide us with sufficient visibility - * information). Thus we must use heap_insert() both during the - * catch-up and here. - * - * 'reform' is a slot to use for tuple "reforming", typically to get set - * values of dropped columns to NULL. - * - * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical - * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops - * this relation, so no logical replication subscription should need the data. - * - * BulkInsertState is used because many tuples are inserted in the typical - * case. - */ -static void -heap_insert_for_repack(Relation rel, TupleTableSlot *src, - TupleTableSlot *reform, BulkInsertStateData *bistate) -{ - HeapTuple tuple; - bool shouldFree; - TupleTableSlot *slot; - - tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); - if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) - { - clear_dropped_attributes(tuple, reform); - slot = reform; - } - else - slot = src; - - /* - * clear_dropped_attributes() should have deformed the tuple, so nothing - * should depend on it now. - */ - if (shouldFree) - heap_freetuple(tuple); - - table_tuple_insert(rel, slot, GetCurrentCommandId(true), - TABLE_INSERT_NO_LOGICAL, bistate); -} - -static bool -tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) -{ - /* - * A short tuple might require values from attmissing val, so activate the - * coding unconditionally in that case. The value might legitimally be - * NULL otherwise, so this is slightly wasteful, but it probably beats - * having to test each attribute for presence of attmissingval each time. - */ - if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) - return true; - - /* Does it have dropped attributes? */ - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped && - !heap_attisnull(tuple, i + 1, tupDesc)) - return true; - } - - return false; -} - -/* - * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. - * - * Set values of dropped columns to NULL, - */ -static void -clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) -{ - TupleDesc tupDesc = reform->tts_tupleDescriptor; - - /* Assuming 'reform' is virtual, this deforms the tuple. */ - Assert(TTS_IS_VIRTUAL(reform)); - ExecForceStoreHeapTuple(tuple, reform, false); - - for (int i = 0; i < tupDesc->natts; i++) - { - if (TupleDescCompactAttr(tupDesc, i)->attisdropped) - reform->tts_isnull[i] = true; - } -} - /* * Check visibility of the tuple. */ diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..19927c15bf3 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -1288,6 +1288,97 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, return OIDNewHeap; } +/* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * 'reform' is a slot to use for tuple "reforming", typically to get set + * values of dropped columns to NULL. + * + * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical + * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops + * this relation, so no logical replication subscription should need the data. + * + * BulkInsertState is used because many tuples are inserted in the typical + * case. + */ +void +heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, BulkInsertStateData *bistate) +{ + HeapTuple tuple; + bool shouldFree; + TupleTableSlot *slot; + + tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree); + if (tuple_needs_reform(tuple, src->tts_tupleDescriptor)) + { + clear_dropped_attributes(tuple, reform); + slot = reform; + } + else + slot = src; + + /* + * clear_dropped_attributes() should have deformed the tuple, so nothing + * should depend on it now. + */ + if (shouldFree) + heap_freetuple(tuple); + + table_tuple_insert(rel, slot, GetCurrentCommandId(true), + TABLE_INSERT_NO_LOGICAL, bistate); +} + +bool +tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc) +{ + /* + * A short tuple might require values from attmissing val, so activate the + * coding unconditionally in that case. The value might legitimally be + * NULL otherwise, so this is slightly wasteful, but it probably beats + * having to test each attribute for presence of attmissingval each time. + */ + if (HeapTupleHeaderGetNatts(tuple->t_data) < tupDesc->natts) + return true; + + /* Does it have dropped attributes? */ + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped && + !heap_attisnull(tuple, i + 1, tupDesc)) + return true; + } + + return false; +} + +/* + * Subroutine for reform_and_rewrite_tuple and heap_insert_for_repack. + * + * Set values of dropped columns to NULL, + */ +void +clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform) +{ + TupleDesc tupDesc = reform->tts_tupleDescriptor; + + /* Assuming 'reform' is virtual, this deforms the tuple. */ + Assert(TTS_IS_VIRTUAL(reform)); + ExecForceStoreHeapTuple(tuple, reform, false); + + for (int i = 0; i < tupDesc->natts; i++) + { + if (TupleDescCompactAttr(tupDesc, i)->attisdropped) + reform->tts_isnull[i] = true; + } +} + /* * Do the physical copying of table data. * diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 45e5440a311..27105c10591 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -15,6 +15,8 @@ #include <signal.h> +#include "access/hio.h" +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" #include "storage/lockdefs.h" @@ -48,6 +50,11 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, char relpersistence, LOCKMODE lockmode); +extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src, + TupleTableSlot *reform, + BulkInsertStateData *bistate); +extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc); +extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, -- 2.52.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v02-0003-Introduce-RepackDest-structure.patch ^ permalink raw reply [nested|flat] 120+ messages in thread
end of thread, other threads:[~2026-07-15 08:37 UTC | newest] Thread overview: 120+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2026-01-27 10:48 [PATCH 3/6] Move conversion of a "historic" to MVCC snapshot to a separate function. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to repack.c. Antonin Houska <[email protected]> 2026-06-16 11:54 [PATCH 2/8] Move functions to 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]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox