XLogRecPtr lsn = record->EndRecPtr;
Page page;
Buffer metabuf;
- ForkNumber forknum;
xl_hash_init_meta_page *xlrec = (xl_hash_init_meta_page *) XLogRecGetData(record);
page = BufferGetPage(metabuf);
PageSetLSN(page, lsn);
MarkBufferDirty(metabuf);
-
- /*
- * Force the on-disk state of init forks to always be in sync with the
- * state in shared buffers. See XLogReadBufferForRedoExtended. We need
- * special handling for init forks as create index operations don't log a
- * full page image of the metapage.
- */
- XLogRecGetBlockTag(record, 0, NULL, &forknum, NULL);
- if (forknum == INIT_FORKNUM)
- FlushOneBuffer(metabuf);
+ XLogFlushBufferForRedoIfInit(record, 0, metabuf);
/* all done */
UnlockReleaseBuffer(metabuf);
Page page;
HashMetaPage metap;
uint32 num_buckets;
- ForkNumber forknum;
xl_hash_init_bitmap_page *xlrec = (xl_hash_init_bitmap_page *) XLogRecGetData(record);
_hash_initbitmapbuffer(bitmapbuf, xlrec->bmsize, true);
PageSetLSN(BufferGetPage(bitmapbuf), lsn);
MarkBufferDirty(bitmapbuf);
-
- /*
- * Force the on-disk state of init forks to always be in sync with the
- * state in shared buffers. See XLogReadBufferForRedoExtended. We need
- * special handling for init forks as create index operations don't log a
- * full page image of the metapage.
- */
- XLogRecGetBlockTag(record, 0, NULL, &forknum, NULL);
- if (forknum == INIT_FORKNUM)
- FlushOneBuffer(bitmapbuf);
+ XLogFlushBufferForRedoIfInit(record, 0, bitmapbuf);
UnlockReleaseBuffer(bitmapbuf);
/* add the new bitmap page to the metapage's list of bitmaps */
PageSetLSN(page, lsn);
MarkBufferDirty(metabuf);
-
- XLogRecGetBlockTag(record, 1, NULL, &forknum, NULL);
- if (forknum == INIT_FORKNUM)
- FlushOneBuffer(metabuf);
+ XLogFlushBufferForRedoIfInit(record, 1, metabuf);
}
if (BufferIsValid(metabuf))
UnlockReleaseBuffer(metabuf);
return buf;
}
+/*
+ * If a redo routine modified an init fork, flush the buffer immediately.
+ *
+ * At the end of crash recovery the init forks of unlogged relations are
+ * copied to the main fork directly from disk, without going through shared
+ * buffers. Therefore, redo routines that update init forks without
+ * restoring a full-page image must call this after setting the page LSN and
+ * marking the buffer dirty.
+ */
+void
+XLogFlushBufferForRedoIfInit(XLogReaderState *record, uint8 block_id,
+ Buffer buffer)
+{
+ ForkNumber forknum;
+
+ Assert(BufferIsValid(buffer));
+
+ XLogRecGetBlockTag(record, block_id, NULL, &forknum, NULL);
+ if (forknum == INIT_FORKNUM)
+ FlushOneBuffer(buffer);
+}
+
/*
* XLogReadBufferForRedoExtended
* Like XLogReadBufferForRedo, but with extra options.
* At the end of crash recovery the init forks of unlogged relations
* are copied, without going through shared buffers. So we need to
* force the on-disk state of init forks to always be in sync with the
- * state in shared buffers.
+ * state in shared buffers. Use XLogFlushBufferForRedoIfInit() for
+ * redo routines that dirty init-fork buffers without restoring a
+ * full-page image.
*/
if (forknum == INIT_FORKNUM)
FlushOneBuffer(*buf);
memcpy(page, localpage, BufferGetPageSize(buffer));
MarkBufferDirty(buffer);
+ XLogFlushBufferForRedoIfInit(record, 0, buffer);
UnlockReleaseBuffer(buffer);
pfree(localpage);
extern XLogRedoAction XLogReadBufferForRedo(XLogReaderState *record,
uint8 block_id, Buffer *buf);
extern Buffer XLogInitBufferForRedo(XLogReaderState *record, uint8 block_id);
+extern void XLogFlushBufferForRedoIfInit(XLogReaderState *record,
+ uint8 block_id, Buffer buffer);
extern XLogRedoAction XLogReadBufferForRedoExtended(XLogReaderState *record,
uint8 block_id,
ReadBufferMode mode, bool get_cleanup_lock,
't/051_effective_wal_level.pl',
't/052_checkpoint_segment_missing.pl',
't/053_standby_login_event_trigger.pl',
+ 't/054_unlogged_sequence_promotion.pl',
],
},
}
--- /dev/null
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Test that unlogged sequences created on a primary can be read after
+# promotion of a standby that replayed their init fork.
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(allows_streaming => 1);
+$node_primary->start;
+
+my $backup_name = 'my_backup';
+$node_primary->backup($backup_name);
+
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+$node_standby->init_from_backup($node_primary, $backup_name,
+ has_streaming => 1);
+$node_standby->start;
+
+# Create the unlogged sequence after the standby has started, so its init fork
+# is generated by WAL replay on the standby.
+$node_primary->safe_psql('postgres', "CREATE UNLOGGED SEQUENCE ulseq");
+$node_primary->wait_for_replay_catchup($node_standby);
+
+$node_standby->promote;
+
+is($node_standby->safe_psql('postgres', "SELECT nextval('ulseq')"),
+ 1, 'unlogged sequence can be read after standby promotion');
+
+done_testing();