From 77bb94e501fc7c5f19b47ed7ccda89bbcaf849ba Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 11:29:39 +0900
Subject: [PATCH v18 6/7] Refactor logic for page manipulations of sequence AMs

This introduces a new header, named sequence_page.h, aimed at providing
helper macros that can be used with sequence implementations that rely
on a single on-disk page.  The in-core "local" sequence AM is one case.
A follow-up patch will rely on that to make its implementation simpler.
---
 src/include/access/sequence_page.h       | 95 ++++++++++++++++++++++++
 src/backend/access/sequence/seqlocalam.c | 52 ++-----------
 2 files changed, 100 insertions(+), 47 deletions(-)
 create mode 100644 src/include/access/sequence_page.h

diff --git a/src/include/access/sequence_page.h b/src/include/access/sequence_page.h
new file mode 100644
index 000000000000..b59f545607cc
--- /dev/null
+++ b/src/include/access/sequence_page.h
@@ -0,0 +1,95 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequence_page.h
+ *	  Helper macros for page manipulations with sequence access methods.
+ *
+ * These macros are useful for sequence access methods that hold their data
+ * on a single page, like the in-core "local" method.
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/sequence_page.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SEQUENCE_PAGE_H
+#define SEQUENCE_PAGE_H
+
+/*
+ * Initialize the first page of a sequence relation.  This embeds the
+ * handling for the special magic number, and enforces a frozen XID,
+ * for VACUUM.
+ *
+ * Since VACUUM does not process sequences, we have to force the tuple to
+ * have xmin = FrozenTransactionId now.  Otherwise it would become
+ * invisible to SELECTs after 2G transactions.  It is okay to do this
+ * because if the current transaction aborts, no other xact will ever
+ * examine the sequence tuple anyway.
+ *
+ * "seqam_special" is the structure used for the special area of a
+ * sequence access method.
+ * "seqam_magic_value" is a value stored in the special area, used for
+ * the validation of the page.
+ */
+#define SEQUENCE_PAGE_INIT(seqam_special, seqam_magic_value) \
+do {																	\
+	buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL,				\
+							EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK);	\
+	Assert(BufferGetBlockNumber(buf) == 0);								\
+																		\
+	page = BufferGetPage(buf);											\
+																		\
+	PageInit(page, BufferGetPageSize(buf), sizeof(seqam_special));		\
+	sm = (seqam_special *) PageGetSpecialPointer(page);					\
+	sm->magic = seqam_magic_value;										\
+																		\
+	/* Now insert sequence tuple */										\
+	HeapTupleHeaderSetXmin(tuple->t_data, FrozenTransactionId);			\
+	HeapTupleHeaderSetXminFrozen(tuple->t_data);						\
+	HeapTupleHeaderSetCmin(tuple->t_data, FirstCommandId);				\
+	HeapTupleHeaderSetXmax(tuple->t_data, InvalidTransactionId);		\
+	tuple->t_data->t_infomask |= HEAP_XMAX_INVALID;						\
+	ItemPointerSet(&tuple->t_data->t_ctid, 0, FirstOffsetNumber);		\
+} while(0)
+
+
+/*
+ * Read the first page of a sequence relation, previously initialized with
+ * SEQUENCE_PAGE_INIT.
+ *
+ * "Form_seqam_data" is the data retrieved from the page.
+ * "seqam_special" is the structure used for the special area of a
+ * sequence access method.
+ * "seqam_magic_value" is a value stored in the special area, used for
+ * the validation of the page.
+ */
+#define SEQUENCE_PAGE_READ(Form_seqam_data, seqam_special, seqam_magic_value) \
+do {																	\
+	Page		page;													\
+	ItemId		lp;														\
+																		\
+	*buf = ReadBuffer(rel, 0);											\
+	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);							\
+																		\
+	page = BufferGetPage(*buf);											\
+	sm = (seqam_special *) PageGetSpecialPointer(page);					\
+																		\
+	if (sm->magic != seqam_magic_value)									\
+		elog(ERROR, "bad magic number in sequence \"%s\": %08X",		\
+			 RelationGetRelationName(rel), sm->magic);					\
+																		\
+	lp = PageGetItemId(page, FirstOffsetNumber);						\
+	Assert(ItemIdIsNormal(lp));											\
+																		\
+	/*																	\
+	 * Note we currently only bother to set these two fields of			\
+	 * *seqdatatuple.													\
+	 */																	\
+	seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);		\
+	seqdatatuple->t_len = ItemIdGetLength(lp);							\
+																		\
+	seq = (Form_seqam_data) GETSTRUCT(seqdatatuple);					\
+} while(0)
+
+#endif							/* SEQUENCE_PAGE_H */
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
index 954749cd8fb6..ed88bc2fafe9 100644
--- a/src/backend/access/sequence/seqlocalam.c
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -18,6 +18,7 @@
 #include "access/multixact.h"
 #include "access/seqlocalam.h"
 #include "access/sequenceam.h"
+#include "access/sequence_page.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -82,27 +83,11 @@ static void fill_seq_fork_with_data(Relation rel, HeapTuple tuple,
 static Form_pg_seq_local_data
 read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 {
-	Page		page;
-	ItemId		lp;
 	seq_local_magic *sm;
 	Form_pg_seq_local_data seq;
 
-	*buf = ReadBuffer(rel, 0);
-	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
-
-	page = BufferGetPage(*buf);
-	sm = (seq_local_magic *) PageGetSpecialPointer(page);
-
-	if (sm->magic != SEQ_LOCAL_MAGIC)
-		elog(ERROR, "bad magic number in sequence \"%s\": %08X",
-			 RelationGetRelationName(rel), sm->magic);
-
-	lp = PageGetItemId(page, FirstOffsetNumber);
-	Assert(ItemIdIsNormal(lp));
-
-	/* Note we currently only bother to set these two fields of *seqdatatuple */
-	seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	seqdatatuple->t_len = ItemIdGetLength(lp);
+	/* Retrieve data from the sequence page */
+	SEQUENCE_PAGE_READ(Form_pg_seq_local_data, seq_local_magic, SEQ_LOCAL_MAGIC);
 
 	/*
 	 * Previous releases of Postgres neglected to prevent SELECT FOR UPDATE on
@@ -121,8 +106,6 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 		MarkBufferDirtyHint(*buf, true);
 	}
 
-	seq = (Form_pg_seq_local_data) GETSTRUCT(seqdatatuple);
-
 	return seq;
 }
 
@@ -161,33 +144,8 @@ fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum)
 	seq_local_magic *sm;
 	OffsetNumber offnum;
 
-	/* Initialize first page of relation with special magic number */
-
-	buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL,
-							EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK);
-	Assert(BufferGetBlockNumber(buf) == 0);
-
-	page = BufferGetPage(buf);
-
-	PageInit(page, BufferGetPageSize(buf), sizeof(seq_local_magic));
-	sm = (seq_local_magic *) PageGetSpecialPointer(page);
-	sm->magic = SEQ_LOCAL_MAGIC;
-
-	/* Now insert sequence tuple */
-
-	/*
-	 * Since VACUUM does not process sequences, we have to force the tuple to
-	 * have xmin = FrozenTransactionId now.  Otherwise it would become
-	 * invisible to SELECTs after 2G transactions.  It is okay to do this
-	 * because if the current transaction aborts, no other xact will ever
-	 * examine the sequence tuple anyway.
-	 */
-	HeapTupleHeaderSetXmin(tuple->t_data, FrozenTransactionId);
-	HeapTupleHeaderSetXminFrozen(tuple->t_data);
-	HeapTupleHeaderSetCmin(tuple->t_data, FirstCommandId);
-	HeapTupleHeaderSetXmax(tuple->t_data, InvalidTransactionId);
-	tuple->t_data->t_infomask |= HEAP_XMAX_INVALID;
-	ItemPointerSet(&tuple->t_data->t_ctid, 0, FirstOffsetNumber);
+	/* Initialize first page of relation */
+	SEQUENCE_PAGE_INIT(seq_local_magic, SEQ_LOCAL_MAGIC);
 
 	/* check the comment above nextval_internal()'s equivalent call. */
 	if (RelationNeedsWAL(rel))
-- 
2.50.1

