0001-heap-Add-RelationGetSpecificBufferForTuple.patch
text/x-patch
Filename: 0001-heap-Add-RelationGetSpecificBufferForTuple.patch
Type: text/x-patch
Part: 4
Message:
Re: Disabling Heap-Only Tuples
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch 0001
Subject: heap: Add RelationGetSpecificBufferForTuple
| File | + | − |
|---|---|---|
| src/backend/access/heap/hio.c | 120 | 0 |
| src/include/access/hio.h | 5 | 0 |
From 2b0357f302a380716f937a7561f44008e98fc99d Mon Sep 17 00:00:00 2001
Message-ID: <2b0357f302a380716f937a7561f44008e98fc99d.1778237699.git.james.locke.uk@gmail.com>
In-Reply-To: <cover.1778237699.git.james.locke.uk@gmail.com>
References: <CAA-aLv6sYZ5XnuYrytTjxZumBh3KrdyMRmasxHfgaKf-HJrNpw@mail.gmail.com>
<cover.1778237699.git.james.locke.uk@gmail.com>
From: James Lock <james.locke.uk@gmail.com>
Date: Thu, 7 May 2026 14:08:50 +0100
Subject: [POC PATCH 1/5] heap: Add RelationGetSpecificBufferForTuple
Provides a thin sibling of RelationGetBufferForTuple for callers that
need to direct a tuple to a specific page rather than letting the FSM
choose. Unlike RelationGetBufferForTuple, this routine never consults
the FSM and never extends the relation; it returns InvalidBuffer if the
requested page no longer has enough free space (which the caller can
recover from by picking a different target).
This is the building block for an upcoming heap compaction phase that
wants to drain tuples out of high-numbered pages onto low-numbered ones,
with the high-water guarantee that the relation is never grown during
the operation.
The locking dance with the source-page buffer mirrors the convention in
RelationGetBufferForTuple (lower block-number first), and visibility-map
pin handling reuses the existing GetVisibilityMapPins helper.
This commit only adds the primitive; no caller exists yet.
---
src/backend/access/heap/hio.c | 120 ++++++++++++++++++++++++++++++++++
src/include/access/hio.h | 5 ++
2 files changed, 125 insertions(+)
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index e96e0f77d92..b4da3531b40 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -882,3 +882,123 @@ loop:
return buffer;
}
+
+/*
+ * RelationGetSpecificBufferForTuple
+ *
+ * Returns pinned and exclusive-locked buffer for the page identified by
+ * requested_target, ready to receive a tuple of size len. Unlike
+ * RelationGetBufferForTuple, the target page is mandatory: this routine
+ * never consults the FSM, never extends the relation, and returns
+ * InvalidBuffer if the requested page does not (any longer) have enough
+ * free space to hold the tuple. The caller is expected to retry with a
+ * different target in that case.
+ *
+ * Intended for callers (e.g. VACUUM (COMPACT)) that need to relocate an
+ * existing tuple to a specific lower-numbered page below a chosen
+ * high-water mark, without risking the relation growing during the
+ * operation.
+ *
+ * otherBuffer, if valid, is the source-page buffer the caller already
+ * holds exclusively-locked; locking order is handled here just as in
+ * RelationGetBufferForTuple, so the caller need not (and must not) hold
+ * the lock on entry -- pass an exclusively-pinned but unlocked buffer.
+ * requested_target must differ from the block held in otherBuffer.
+ *
+ * *vmbuffer / *vmbuffer_other are visibility-map buffer slots managed in
+ * the same manner as RelationGetBufferForTuple.
+ */
+Buffer
+RelationGetSpecificBufferForTuple(Relation relation, Size len,
+ BlockNumber requested_target,
+ Buffer otherBuffer,
+ Buffer *vmbuffer, Buffer *vmbuffer_other)
+{
+ Buffer buffer;
+ Page page;
+ Size pageFreeSpace;
+ BlockNumber otherBlock;
+
+ len = MAXALIGN(len);
+
+ if (len > MaxHeapTupleSize)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("row is too big: size %zu, maximum size %zu",
+ len, MaxHeapTupleSize)));
+
+ Assert(BlockNumberIsValid(requested_target));
+ Assert(requested_target < RelationGetNumberOfBlocks(relation));
+
+ if (otherBuffer != InvalidBuffer)
+ {
+ otherBlock = BufferGetBlockNumber(otherBuffer);
+ if (otherBlock == requested_target)
+ elog(ERROR, "compaction target page must differ from source page");
+ }
+ else
+ otherBlock = InvalidBlockNumber;
+
+ /*
+ * Pin the target page, check the all-visible flag (without the lock; we
+ * recheck after acquiring it via GetVisibilityMapPins), and acquire the
+ * page locks in block-number order to match RelationGetBufferForTuple's
+ * convention.
+ */
+ buffer = ReadBuffer(relation, requested_target);
+ if (PageIsAllVisible(BufferGetPage(buffer)))
+ visibilitymap_pin(relation, requested_target, vmbuffer);
+
+ if (otherBuffer == InvalidBuffer)
+ {
+ LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+ }
+ else if (otherBlock < requested_target)
+ {
+ LockBuffer(otherBuffer, BUFFER_LOCK_EXCLUSIVE);
+ LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+ }
+ else
+ {
+ LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+ LockBuffer(otherBuffer, BUFFER_LOCK_EXCLUSIVE);
+ }
+
+ /*
+ * Recheck visibility-map pins now that we hold the locks. This may
+ * temporarily release the locks to acquire pins, in which case the page
+ * could fill up underneath us; the recheck below catches that.
+ */
+ GetVisibilityMapPins(relation, buffer, otherBuffer,
+ requested_target, otherBlock,
+ vmbuffer, vmbuffer_other);
+
+ page = BufferGetPage(buffer);
+
+ /*
+ * Initialise a brand-new page if necessary. This shouldn't normally
+ * happen for a compaction target -- callers pick existing in-use pages
+ * -- but be defensive.
+ */
+ if (PageIsNew(page))
+ {
+ PageInit(page, BufferGetPageSize(buffer), 0);
+ MarkBufferDirty(buffer);
+ }
+
+ pageFreeSpace = PageGetHeapFreeSpace(page);
+ if (len > pageFreeSpace)
+ {
+ /*
+ * Lost the race: the page no longer has room. Release everything
+ * and let the caller pick a different target. We don't release the
+ * caller's pin on otherBuffer, only the lock we acquired above.
+ */
+ if (otherBuffer != InvalidBuffer)
+ LockBuffer(otherBuffer, BUFFER_LOCK_UNLOCK);
+ UnlockReleaseBuffer(buffer);
+ return InvalidBuffer;
+ }
+
+ return buffer;
+}
diff --git a/src/include/access/hio.h b/src/include/access/hio.h
index 60cfc375fd5..97d02c3a5d6 100644
--- a/src/include/access/hio.h
+++ b/src/include/access/hio.h
@@ -58,5 +58,10 @@ extern Buffer RelationGetBufferForTuple(Relation relation, Size len,
BulkInsertStateData *bistate,
Buffer *vmbuffer, Buffer *vmbuffer_other,
int num_pages);
+extern Buffer RelationGetSpecificBufferForTuple(Relation relation, Size len,
+ BlockNumber requested_target,
+ Buffer otherBuffer,
+ Buffer *vmbuffer,
+ Buffer *vmbuffer_other);
#endif /* HIO_H */
--
2.47.3