From e13ef14d78c72cae1dfdf0d291228b807670c0f8 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Thu, 21 Aug 2025 11:29:39 +0900 Subject: [PATCH v19 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.51.0