public inbox for [email protected]  
help / color / mirror / Atom feed
Re: Sequence Access Methods, round two
3+ messages / 2 participants
[nested] [flat]

* Re: Sequence Access Methods, round two
@ 2026-01-07 05:32 Michael Paquier <[email protected]>
  2026-05-18 18:42 ` Re: Sequence Access Methods, round two Andrei Lepikhov <[email protected]>
  0 siblings, 1 reply; 3+ messages in thread

From: Michael Paquier @ 2026-01-07 05:32 UTC (permalink / raw)
  To: Chao Li <[email protected]>; +Cc: Xuneng Zhou <[email protected]>; Andrei Lepikhov <[email protected]>; Peter Eisentraut <[email protected]>; Kirill Reshke <[email protected]>; Peter Smith <[email protected]>; Postgres hackers <[email protected]>

On Fri, Dec 26, 2025 at 02:41:01PM +0800, Chao Li wrote:
> Then that has to be documented clearly. Meaning that, if a user
> chooses to use a non-default sequence AM, then UNLOGGED might not
> work, I don’t see a mechanism to enforce a custom AM to honor
> UNLOGGED.

Perhaps.  At the end, I am not sure if we really need to get down to
that in the docs.  It is really up to an AM to decide what should
happen depending on the relpersistence of the Relation.

> Should the macro comment documents something like: “to use this
> macro, the caller must define the following variables 1) Buffer
> buf;, 2) Page page;, 3) seam_speical *sm;” If we use a function, the
> interface is clear; to use a macro, the interface and dependencies
> are not that clear, so more documentation is needed.

I think that's pretty obvious once one uses this code, as they would
likely use the example module in contrib/ as a base for their own
work.  :)

For now, I am sending a rebased v28.  This stuff needed a refresh.
--
Michael


Attachments:

  [text/x-diff] v28-0001-Integrate-addition-of-attributes-for-sequences-w.patch (11.2K, 2-v28-0001-Integrate-addition-of-attributes-for-sequences-w.patch)
  download | inline diff:
From ae717c46467bf727c9ac203079750c65eaf4cf9e Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
Date: Fri, 26 Dec 2025 11:15:22 +0900
Subject: [PATCH v28 1/7] Integrate addition of attributes for sequences with
 ALTER TABLE

This is a process similar to CREATE OR REPLACE VIEW, where attributes
are added to a sequence after the initial creation of its Relation.
This gives more flexibility to sequence AMs, as these may want to force
their own set of attributes to use when coupled with their computation
methods and/or underlying table AM.
---
 src/include/nodes/parsenodes.h                |  1 +
 src/backend/commands/sequence.c               | 31 +++++++++++++++++--
 src/backend/commands/tablecmds.c              | 10 ++++++
 src/backend/tcop/utility.c                    |  4 +++
 .../test_ddl_deparse/expected/alter_table.out | 10 ++++--
 .../expected/create_sequence.out              |  5 ++-
 .../expected/create_table.out                 | 15 +++++++--
 .../test_ddl_deparse/test_ddl_deparse.c       |  3 ++
 8 files changed, 71 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index aac4bfc70d99..0eba5ceb18a3 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2442,6 +2442,7 @@ typedef struct AlterTableStmt
 typedef enum AlterTableType
 {
 	AT_AddColumn,				/* add column */
+	AT_AddColumnToSequence,		/* implicitly via CREATE SEQUENCE */
 	AT_AddColumnToView,			/* implicitly via CREATE OR REPLACE VIEW */
 	AT_ColumnDefault,			/* alter column default */
 	AT_CookedColumnDefault,		/* add a pre-cooked column default */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 904eeada5ab5..4c5135c388dd 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -125,6 +125,9 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	TupleDesc	tupDesc;
 	Datum		value[SEQ_COL_LASTCOL];
 	bool		null[SEQ_COL_LASTCOL];
+	List	   *elts = NIL;
+	List	   *atcmds = NIL;
+	ListCell   *lc;
 	Datum		pgs_values[Natts_pg_sequence];
 	bool		pgs_nulls[Natts_pg_sequence];
 	int			i;
@@ -163,7 +166,6 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	/*
 	 * Create relation (and fill value[] and null[] for the tuple)
 	 */
-	stmt->tableElts = NIL;
 	for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++)
 	{
 		ColumnDef  *coldef = NULL;
@@ -187,7 +189,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 		coldef->is_not_null = true;
 		null[i - 1] = false;
 
-		stmt->tableElts = lappend(stmt->tableElts, coldef);
+		elts = lappend(elts, coldef);
 	}
 
 	stmt->relation = seq->sequence;
@@ -198,11 +200,36 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
 
+	/*
+	 * Initial relation has no attributes, these are added later after the
+	 * relation has been created in the catalogs.
+	 */
+	stmt->tableElts = NIL;
+
 	address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
 	seqoid = address.objectId;
 	Assert(seqoid != InvalidOid);
 
 	rel = sequence_open(seqoid, AccessExclusiveLock);
+
+	/* Add all the attributes to the sequence */
+	foreach(lc, elts)
+	{
+		AlterTableCmd *atcmd;
+
+		atcmd = makeNode(AlterTableCmd);
+		atcmd->subtype = AT_AddColumnToSequence;
+		atcmd->def = (Node *) lfirst(lc);
+		atcmds = lappend(atcmds, atcmd);
+	}
+
+	/*
+	 * No recursion needed.  Note that EventTriggerAlterTableStart() should
+	 * have been called.
+	 */
+	AlterTableInternal(RelationGetRelid(rel), atcmds, false);
+	CommandCounterIncrement();
+
 	tupDesc = RelationGetDescr(rel);
 
 	/* now initialize the sequence's data */
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f976c0e5c7ea..9cb90c9bffa2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4654,6 +4654,7 @@ AlterTableGetLockLevel(List *cmds)
 				 * Subcommands that may be visible to concurrent SELECTs
 				 */
 			case AT_DropColumn: /* change visible to SELECT */
+			case AT_AddColumnToSequence:	/* CREATE SEQUENCE */
 			case AT_AddColumnToView:	/* CREATE VIEW */
 			case AT_DropOids:	/* used to equiv to DropColumn */
 			case AT_EnableAlwaysRule:	/* may change SELECT rules */
@@ -4954,6 +4955,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			/* Recursion occurs during execution phase */
 			pass = AT_PASS_ADD_COL;
 			break;
+		case AT_AddColumnToSequence:	/* add column via CREATE SEQUENCE */
+			ATSimplePermissions(cmd->subtype, rel, ATT_SEQUENCE);
+			ATPrepAddColumn(wqueue, rel, recurse, recursing, false, cmd,
+							lockmode, context);
+			/* Recursion occurs during execution phase */
+			pass = AT_PASS_ADD_COL;
+			break;
 		case AT_AddColumnToView:	/* add column via CREATE OR REPLACE VIEW */
 			ATSimplePermissions(cmd->subtype, rel, ATT_VIEW);
 			ATPrepAddColumn(wqueue, rel, recurse, recursing, true, cmd,
@@ -5392,6 +5400,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
 	switch (cmd->subtype)
 	{
 		case AT_AddColumn:		/* ADD COLUMN */
+		case AT_AddColumnToSequence:	/* add column via CREATE SEQUENCE */
 		case AT_AddColumnToView:	/* add column via CREATE OR REPLACE VIEW */
 			address = ATExecAddColumn(wqueue, tab, rel, &cmd,
 									  cmd->recurse, false,
@@ -6623,6 +6632,7 @@ alter_table_type_to_string(AlterTableType cmdtype)
 	switch (cmdtype)
 	{
 		case AT_AddColumn:
+		case AT_AddColumnToSequence:
 		case AT_AddColumnToView:
 			return "ADD COLUMN";
 		case AT_ColumnDefault:
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 34dd6e18df56..bfa5adf6e5f5 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1667,7 +1667,11 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateSeqStmt:
+				EventTriggerAlterTableStart(parsetree);
 				address = DefineSequence(pstate, (CreateSeqStmt *) parsetree);
+				/* stashed internally */
+				commandCollected = true;
+				EventTriggerAlterTableEnd();
 				break;
 
 			case T_AlterSeqStmt:
diff --git a/src/test/modules/test_ddl_deparse/expected/alter_table.out b/src/test/modules/test_ddl_deparse/expected/alter_table.out
index 3a2f576f3b6e..92403ef33f25 100644
--- a/src/test/modules/test_ddl_deparse/expected/alter_table.out
+++ b/src/test/modules/test_ddl_deparse/expected/alter_table.out
@@ -25,7 +25,10 @@ NOTICE:  DDL test: type simple, tag CREATE TABLE
 CREATE TABLE grandchild () INHERITS (child);
 NOTICE:  DDL test: type simple, tag CREATE TABLE
 ALTER TABLE parent ADD COLUMN b serial;
-NOTICE:  DDL test: type simple, tag CREATE SEQUENCE
+NOTICE:  DDL test: type alter table, tag CREATE SEQUENCE
+NOTICE:    subcommand: type ADD COLUMN TO SEQUENCE desc column last_value of sequence parent_b_seq
+NOTICE:    subcommand: type ADD COLUMN TO SEQUENCE desc column log_cnt of sequence parent_b_seq
+NOTICE:    subcommand: type ADD COLUMN TO SEQUENCE desc column is_called of sequence parent_b_seq
 NOTICE:  DDL test: type alter table, tag ALTER TABLE
 NOTICE:    subcommand: type ADD COLUMN (and recurse) desc column b of table parent
 NOTICE:    subcommand: type ADD CONSTRAINT (and recurse) desc constraint parent_b_not_null on table parent
@@ -51,7 +54,10 @@ ALTER TABLE parent ALTER COLUMN a SET NOT NULL;
 NOTICE:  DDL test: type alter table, tag ALTER TABLE
 NOTICE:    subcommand: type SET NOT NULL (and recurse) desc constraint parent_a_not_null on table parent
 ALTER TABLE parent ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY;
-NOTICE:  DDL test: type simple, tag CREATE SEQUENCE
+NOTICE:  DDL test: type alter table, tag CREATE SEQUENCE
+NOTICE:    subcommand: type ADD COLUMN TO SEQUENCE desc column last_value of sequence parent_a_seq
+NOTICE:    subcommand: type ADD COLUMN TO SEQUENCE desc column log_cnt of sequence parent_a_seq
+NOTICE:    subcommand: type ADD COLUMN TO SEQUENCE desc column is_called of sequence parent_a_seq
 NOTICE:  DDL test: type simple, tag ALTER SEQUENCE
 NOTICE:  DDL test: type alter table, tag ALTER TABLE
 NOTICE:    subcommand: type ADD IDENTITY (and recurse) desc column a of table parent
diff --git a/src/test/modules/test_ddl_deparse/expected/create_sequence.out b/src/test/modules/test_ddl_deparse/expected/create_sequence.out
index 5837ea484e40..310ce5a6baf5 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_sequence.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_sequence.out
@@ -8,4 +8,7 @@ CREATE SEQUENCE fkey_table_seq
   START 10
   CACHE 10
   CYCLE;
-NOTICE:  DDL test: type simple, tag CREATE SEQUENCE
+NOTICE:  DDL test: type alter table, tag CREATE SEQUENCE
+NOTICE:    subcommand: type ADD COLUMN TO SEQUENCE desc column last_value of sequence fkey_table_seq
+NOTICE:    subcommand: type ADD COLUMN TO SEQUENCE desc column log_cnt of sequence fkey_table_seq
+NOTICE:    subcommand: type ADD COLUMN TO SEQUENCE desc column is_called of sequence fkey_table_seq
diff --git a/src/test/modules/test_ddl_deparse/expected/create_table.out b/src/test/modules/test_ddl_deparse/expected/create_table.out
index 14915f661a89..527c67995a94 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_table.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_table.out
@@ -50,9 +50,18 @@ CREATE TABLE datatype_table (
     PRIMARY KEY (id),
     UNIQUE (id_big)
 );
-NOTICE:  DDL test: type simple, tag CREATE SEQUENCE
-NOTICE:  DDL test: type simple, tag CREATE SEQUENCE
-NOTICE:  DDL test: type simple, tag CREATE SEQUENCE
+NOTICE:  DDL test: type alter table, tag CREATE SEQUENCE
+NOTICE:    subcommand: type ADD COLUMN TO SEQUENCE desc column last_value of sequence datatype_table_id_seq
+NOTICE:    subcommand: type ADD COLUMN TO SEQUENCE desc column log_cnt of sequence datatype_table_id_seq
+NOTICE:    subcommand: type ADD COLUMN TO SEQUENCE desc column is_called of sequence datatype_table_id_seq
+NOTICE:  DDL test: type alter table, tag CREATE SEQUENCE
+NOTICE:    subcommand: type ADD COLUMN TO SEQUENCE desc column last_value of sequence datatype_table_id_big_seq
+NOTICE:    subcommand: type ADD COLUMN TO SEQUENCE desc column log_cnt of sequence datatype_table_id_big_seq
+NOTICE:    subcommand: type ADD COLUMN TO SEQUENCE desc column is_called of sequence datatype_table_id_big_seq
+NOTICE:  DDL test: type alter table, tag CREATE SEQUENCE
+NOTICE:    subcommand: type ADD COLUMN TO SEQUENCE desc column last_value of sequence datatype_table_is_small_seq
+NOTICE:    subcommand: type ADD COLUMN TO SEQUENCE desc column log_cnt of sequence datatype_table_is_small_seq
+NOTICE:    subcommand: type ADD COLUMN TO SEQUENCE desc column is_called of sequence datatype_table_is_small_seq
 NOTICE:  DDL test: type simple, tag CREATE TABLE
 NOTICE:  DDL test: type simple, tag CREATE INDEX
 NOTICE:  DDL test: type simple, tag CREATE INDEX
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
index 380b3e754b7a..49115a9d3ed7 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -113,6 +113,9 @@ get_altertable_subcmdinfo(PG_FUNCTION_ARGS)
 			case AT_AddColumn:
 				strtype = "ADD COLUMN";
 				break;
+			case AT_AddColumnToSequence:
+				strtype = "ADD COLUMN TO SEQUENCE";
+				break;
 			case AT_AddColumnToView:
 				strtype = "ADD COLUMN TO VIEW";
 				break;
-- 
2.51.0



  [text/x-diff] v28-0002-Refactor-code-for-in-core-local-sequences.patch (56.4K, 3-v28-0002-Refactor-code-for-in-core-local-sequences.patch)
  download | inline diff:
From 5a56683838b4950902106e035e0b18605422320a Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
Date: Wed, 7 Jan 2026 14:21:48 +0900
Subject: [PATCH v28 2/7] Refactor code for in-core "local" sequences

This commit restructures the code of in-core sequences into a new set of
files:
- seqdesc.c is renamed to seqlocaldesc.c.
- seqlocal_xlog.c to the code in the WAL replay logic.
- seqlocalam.c to store a set of routines called from sequence.c,
finishing the separation between the main sequence logic and the in-core
sequences.
- seqlocalam.h to store the AM-specific structures and routine related
to the in-core sequences.

WAL records are renamed to "SequenceLocal" with structures, variables
and file structures mapping to that.
---
 src/include/access/rmgrlist.h                 |   2 +-
 src/include/access/seqlocal_xlog.h            |  45 ++
 src/include/access/seqlocalam.h               |  32 +
 src/include/commands/sequence_xlog.h          |  45 --
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 .../rmgrdesc/{seqdesc.c => seqlocaldesc.c}    |  20 +-
 src/backend/access/sequence/Makefile          |   4 +-
 src/backend/access/sequence/meson.build       |   2 +
 .../sequence/seqlocal_xlog.c}                 |  34 +-
 src/backend/access/sequence/seqlocalam.c      | 635 ++++++++++++++++++
 src/backend/access/transam/rmgr.c             |   2 +-
 src/backend/commands/Makefile                 |   1 -
 src/backend/commands/meson.build              |   1 -
 src/backend/commands/sequence.c               | 564 +---------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   2 +-
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 18 files changed, 778 insertions(+), 619 deletions(-)
 create mode 100644 src/include/access/seqlocal_xlog.h
 create mode 100644 src/include/access/seqlocalam.h
 delete mode 100644 src/include/commands/sequence_xlog.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%)
 rename src/backend/{commands/sequence_xlog.c => access/sequence/seqlocal_xlog.c} (67%)
 create mode 100644 src/backend/access/sequence/seqlocalam.c

diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 3352b5f8532a..47eade9212f0 100644
--- a/src/include/access/rmgrlist.h
+++ b/src/include/access/rmgrlist.h
@@ -40,7 +40,7 @@ PG_RMGR(RM_BTREE_ID, "Btree", btree_redo, btree_desc, btree_identify, btree_xlog
 PG_RMGR(RM_HASH_ID, "Hash", hash_redo, hash_desc, hash_identify, NULL, NULL, hash_mask, NULL)
 PG_RMGR(RM_GIN_ID, "Gin", gin_redo, gin_desc, gin_identify, gin_xlog_startup, gin_xlog_cleanup, gin_mask, NULL)
 PG_RMGR(RM_GIST_ID, "Gist", gist_redo, gist_desc, gist_identify, gist_xlog_startup, gist_xlog_cleanup, gist_mask, NULL)
-PG_RMGR(RM_SEQ_ID, "Sequence", seq_redo, seq_desc, seq_identify, NULL, NULL, seq_mask, NULL)
+PG_RMGR(RM_SEQ_LOCAL_ID, "SequenceLocal", seq_local_redo, seq_local_desc, seq_local_identify, NULL, NULL, seq_local_mask, NULL)
 PG_RMGR(RM_SPGIST_ID, "SPGist", spg_redo, spg_desc, spg_identify, spg_xlog_startup, spg_xlog_cleanup, spg_mask, NULL)
 PG_RMGR(RM_BRIN_ID, "BRIN", brin_redo, brin_desc, brin_identify, NULL, NULL, brin_mask, NULL)
 PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commit_ts_identify, NULL, NULL, NULL, NULL)
diff --git a/src/include/access/seqlocal_xlog.h b/src/include/access/seqlocal_xlog.h
new file mode 100644
index 000000000000..4f2441a8ca54
--- /dev/null
+++ b/src/include/access/seqlocal_xlog.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocal_xlog.h
+ *	  Local sequence WAL definitions.
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/seqlocal_xlog.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef SEQLOCAL_XLOG_H
+#define SEQLOCAL_XLOG_H
+
+#include "access/xlogreader.h"
+#include "lib/stringinfo.h"
+
+/* Record identifier */
+#define XLOG_SEQ_LOCAL_LOG			0x00
+
+/*
+ * The "special area" of a local sequence's buffer page looks like this.
+ */
+#define SEQ_LOCAL_MAGIC	  0x1717
+
+typedef struct seq_local_magic
+{
+	uint32		magic;
+} seq_local_magic;
+
+/* Sequence WAL record */
+typedef struct xl_seq_local_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_seq_local_rec;
+
+extern void seq_local_redo(XLogReaderState *record);
+extern void seq_local_desc(StringInfo buf, XLogReaderState *record);
+extern const char *seq_local_identify(uint8 info);
+extern void seq_local_mask(char *page, BlockNumber blkno);
+
+#endif							/* SEQLOCAL_XLOG_H */
diff --git a/src/include/access/seqlocalam.h b/src/include/access/seqlocalam.h
new file mode 100644
index 000000000000..2ce54c2b7789
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/seqlocalam.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SEQLOCALAM_H
+#define SEQLOCALAM_H
+
+#include "utils/rel.h"
+
+/* access routines */
+extern int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv,
+							   int64 minv, int64 cache, bool cycle,
+							   int64 *last);
+extern const char *seq_local_get_table_am(void);
+extern void seq_local_init(Relation rel, int64 last_value, bool is_called);
+extern void seq_local_setval(Relation rel, int64 next, bool iscalled);
+extern void seq_local_reset(Relation rel, int64 startv, bool is_called,
+							bool reset_state);
+extern void seq_local_get_state(Relation rel, int64 *last_value,
+								bool *is_called, XLogRecPtr *page_lsn);
+extern void seq_local_change_persistence(Relation rel,
+										 char newrelpersistence);
+
+#endif							/* SEQLOCALAM_H */
diff --git a/src/include/commands/sequence_xlog.h b/src/include/commands/sequence_xlog.h
deleted file mode 100644
index b0495f41b43d..000000000000
--- a/src/include/commands/sequence_xlog.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * sequence_xlog.h
- *	  Sequence WAL definitions.
- *
- * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * src/include/commands/sequence_xlog.h
- *
- *-------------------------------------------------------------------------
- */
-
-#ifndef SEQUENCE_XLOG_H
-#define SEQUENCE_XLOG_H
-
-#include "access/xlogreader.h"
-#include "lib/stringinfo.h"
-
-/* Record identifier */
-#define XLOG_SEQ_LOG			0x00
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC		0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
-/* Sequence WAL record */
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
-extern void seq_redo(XLogReaderState *record);
-extern void seq_desc(StringInfo buf, XLogReaderState *record);
-extern const char *seq_identify(uint8 info);
-extern void seq_mask(char *page, BlockNumber blkno);
-
-#endif							/* SEQUENCE_XLOG_H */
diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile
index cd95eec37f14..e5900ed77af5 100644
--- a/src/backend/access/rmgrdesc/Makefile
+++ b/src/backend/access/rmgrdesc/Makefile
@@ -24,7 +24,7 @@ OBJS = \
 	relmapdesc.o \
 	replorigindesc.o \
 	rmgrdesc_utils.o \
-	seqdesc.o \
+	seqlocaldesc.o \
 	smgrdesc.o \
 	spgdesc.o \
 	standbydesc.o \
diff --git a/src/backend/access/rmgrdesc/meson.build b/src/backend/access/rmgrdesc/meson.build
index d9000ccd9fd1..7a59bb082376 100644
--- a/src/backend/access/rmgrdesc/meson.build
+++ b/src/backend/access/rmgrdesc/meson.build
@@ -17,7 +17,7 @@ rmgr_desc_sources = files(
   'relmapdesc.c',
   'replorigindesc.c',
   'rmgrdesc_utils.c',
-  'seqdesc.c',
+  'seqlocaldesc.c',
   'smgrdesc.c',
   'spgdesc.c',
   'standbydesc.c',
diff --git a/src/backend/access/rmgrdesc/seqdesc.c b/src/backend/access/rmgrdesc/seqlocaldesc.c
similarity index 64%
rename from src/backend/access/rmgrdesc/seqdesc.c
rename to src/backend/access/rmgrdesc/seqlocaldesc.c
index c9fc6dc18503..7aeb3f7cf4be 100644
--- a/src/backend/access/rmgrdesc/seqdesc.c
+++ b/src/backend/access/rmgrdesc/seqlocaldesc.c
@@ -1,44 +1,44 @@
 /*-------------------------------------------------------------------------
  *
- * seqdesc.c
- *	  rmgr descriptor routines for commands/sequence.c
+ * seqlocaldesc.c
+ *	  rmgr descriptor routines for sequence/seqlocal_xlog.c
  *
  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
  * IDENTIFICATION
- *	  src/backend/access/rmgrdesc/seqdesc.c
+ *	  src/backend/access/rmgrdesc/seqlocaldesc.c
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
-#include "commands/sequence_xlog.h"
+#include "access/seqlocal_xlog.h"
 
 
 void
-seq_desc(StringInfo buf, XLogReaderState *record)
+seq_local_desc(StringInfo buf, XLogReaderState *record)
 {
 	char	   *rec = XLogRecGetData(record);
 	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	xl_seq_rec *xlrec = (xl_seq_rec *) rec;
+	xl_seq_local_rec *xlrec = (xl_seq_local_rec *) rec;
 
-	if (info == XLOG_SEQ_LOG)
+	if (info == XLOG_SEQ_LOCAL_LOG)
 		appendStringInfo(buf, "rel %u/%u/%u",
 						 xlrec->locator.spcOid, xlrec->locator.dbOid,
 						 xlrec->locator.relNumber);
 }
 
 const char *
-seq_identify(uint8 info)
+seq_local_identify(uint8 info)
 {
 	const char *id = NULL;
 
 	switch (info & ~XLR_INFO_MASK)
 	{
-		case XLOG_SEQ_LOG:
-			id = "LOG";
+		case XLOG_SEQ_LOCAL_LOG:
+			id = "SEQ_LOCAL_LOG";
 			break;
 	}
 
diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile
index 9f9d31f5425a..2a3c8542cb33 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,8 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = seqlocalam.o \
+	seqlocal_xlog.o \
+	sequence.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build
index 40cc02c770c0..dea3b597a889 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2026, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'seqlocalam.c',
+  'seqlocal_xlog.c',
   'sequence.c',
 )
diff --git a/src/backend/commands/sequence_xlog.c b/src/backend/access/sequence/seqlocal_xlog.c
similarity index 67%
rename from src/backend/commands/sequence_xlog.c
rename to src/backend/access/sequence/seqlocal_xlog.c
index d0aed48e2680..40c72cef19ae 100644
--- a/src/backend/commands/sequence_xlog.c
+++ b/src/backend/access/sequence/seqlocal_xlog.c
@@ -1,26 +1,26 @@
 /*-------------------------------------------------------------------------
  *
- * sequence.c
- *	  RMGR WAL routines for sequences.
+ * seqlocal_xlog.c
+ *	  WAL replay logic for local sequence access manager
  *
  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
  * IDENTIFICATION
- *	  src/backend/commands/sequence_xlog.c
+ *	  src/backend/access/sequence/seqlocal_xlog.c
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
 #include "access/bufmask.h"
+#include "access/seqlocal_xlog.h"
 #include "access/xlogutils.h"
-#include "commands/sequence_xlog.h"
-#include "storage/bufmgr.h"
+#include "storage/block.h"
 
 void
-seq_redo(XLogReaderState *record)
+seq_local_redo(XLogReaderState *record)
 {
 	XLogRecPtr	lsn = record->EndRecPtr;
 	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
@@ -29,11 +29,11 @@ seq_redo(XLogReaderState *record)
 	Page		localpage;
 	char	   *item;
 	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
+	xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record);
+	seq_local_magic *sm;
 
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
+	if (info != XLOG_SEQ_LOCAL_LOG)
+		elog(PANIC, "seq_local_redo: unknown op code %u", info);
 
 	buffer = XLogInitBufferForRedo(record, 0);
 	page = BufferGetPage(buffer);
@@ -49,15 +49,15 @@ seq_redo(XLogReaderState *record)
 	 */
 	localpage = (Page) palloc(BufferGetPageSize(buffer));
 
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
+	PageInit(localpage, BufferGetPageSize(buffer), sizeof(seq_local_magic));
+	sm = (seq_local_magic *) PageGetSpecialPointer(localpage);
+	sm->magic = SEQ_LOCAL_MAGIC;
 
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
+	item = (char *) xlrec + sizeof(xl_seq_local_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec);
 
 	if (PageAddItem(localpage, item, itemsz, FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
+		elog(PANIC, "seq_local_redo: failed to add item to page");
 
 	PageSetLSN(localpage, lsn);
 
@@ -72,7 +72,7 @@ seq_redo(XLogReaderState *record)
  * Mask a Sequence page before performing consistency checks on it.
  */
 void
-seq_mask(char *page, BlockNumber blkno)
+seq_local_mask(char *page, BlockNumber blkno)
 {
 	mask_page_lsn_and_checksum(page);
 
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
new file mode 100644
index 000000000000..2b7bfb68d51d
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -0,0 +1,635 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.c
+ *	  Local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/access/sequence/seqlocalam.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/seqlocalam.h"
+#include "access/seqlocal_xlog.h"
+#include "access/tableam.h"
+#include "access/xact.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage_xlog.h"
+#include "commands/tablecmds.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+
+
+/* Format of tuples stored in heap table associated to local sequences */
+typedef struct FormData_pg_seq_local_data
+{
+	int64		last_value;
+	int64		log_cnt;
+	bool		is_called;
+} FormData_pg_seq_local_data;
+
+typedef FormData_pg_seq_local_data *Form_pg_seq_local_data;
+
+/*
+ * Columns of a local sequence relation
+ */
+#define SEQ_LOCAL_COL_LASTVAL			1
+#define SEQ_LOCAL_COL_LOG				2
+#define SEQ_LOCAL_COL_CALLED			3
+
+#define SEQ_LOCAL_COL_FIRSTCOL		SEQ_LOCAL_COL_LASTVAL
+#define SEQ_LOCAL_COL_LASTCOL		SEQ_LOCAL_COL_CALLED
+
+
+/*
+ * We don't want to log each fetching of a value from a sequence,
+ * so we pre-log a few fetches in advance. In the event of
+ * crash we can lose (skip over) as many values as we pre-logged.
+ */
+#define SEQ_LOCAL_LOG_VALS		32
+
+static Form_pg_seq_local_data read_seq_tuple(Relation rel,
+											 Buffer *buf,
+											 HeapTuple seqdatatuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_fork_with_data(Relation rel, HeapTuple tuple,
+									ForkNumber forkNum);
+
+/*
+ * Given an opened sequence relation, lock the page buffer and find the tuple
+ *
+ * *buf receives the reference to the pinned-and-ex-locked buffer
+ * *seqdatatuple receives the reference to the sequence tuple proper
+ *		(this arg should point to a local variable of type HeapTupleData)
+ *
+ * Function's return value points to the data payload of the 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);
+
+	/*
+	 * Previous releases of Postgres neglected to prevent SELECT FOR UPDATE on
+	 * a sequence, which would leave a non-frozen XID in the sequence tuple's
+	 * xmax, which eventually leads to clog access failures or worse. If we
+	 * see this has happened, clean up after it.  We treat this like a hint
+	 * bit update, ie, don't bother to WAL-log it, since we can certainly do
+	 * this again if the update gets lost.
+	 */
+	Assert(!(seqdatatuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI));
+	if (HeapTupleHeaderGetRawXmax(seqdatatuple->t_data) != InvalidTransactionId)
+	{
+		HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId);
+		seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED;
+		seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID;
+		MarkBufferDirtyHint(*buf, true);
+	}
+
+	seq = (Form_pg_seq_local_data) GETSTRUCT(seqdatatuple);
+
+	return seq;
+}
+
+/*
+ * Initialize a sequence's relation with the specified tuple as content
+ *
+ * This handles unlogged sequences by writing to both the main and the init
+ * fork as necessary.
+ */
+static void
+fill_seq_with_data(Relation rel, HeapTuple tuple)
+{
+	fill_seq_fork_with_data(rel, tuple, MAIN_FORKNUM);
+
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
+	{
+		SMgrRelation srel;
+
+		srel = smgropen(rel->rd_locator, INVALID_PROC_NUMBER);
+		smgrcreate(srel, INIT_FORKNUM, false);
+		log_smgrcreate(&rel->rd_locator, INIT_FORKNUM);
+		fill_seq_fork_with_data(rel, tuple, INIT_FORKNUM);
+		FlushRelationBuffers(rel);
+		smgrclose(srel);
+	}
+}
+
+/*
+ * Initialize a sequence's relation fork with the specified tuple as content
+ */
+static void
+fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum)
+{
+	Buffer		buf;
+	Page		page;
+	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);
+
+	/* check the comment above nextval_internal()'s equivalent call. */
+	if (RelationNeedsWAL(rel))
+		GetTopTransactionId();
+
+	START_CRIT_SECTION();
+
+	MarkBufferDirty(buf);
+
+	offnum = PageAddItem(page, tuple->t_data, tuple->t_len, InvalidOffsetNumber, false, false);
+	if (offnum != FirstOffsetNumber)
+		elog(ERROR, "failed to add sequence tuple to page");
+
+	/* XLOG stuff */
+	if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM)
+	{
+		xl_seq_local_rec xlrec;
+		XLogRecPtr	recptr;
+
+		XLogBeginInsert();
+		XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
+
+		xlrec.locator = rel->rd_locator;
+
+		XLogRegisterData(&xlrec, sizeof(xl_seq_local_rec));
+		XLogRegisterData(tuple->t_data, tuple->t_len);
+
+		recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buf);
+}
+
+/*
+ * seq_local_nextval()
+ *
+ * Allocate a new value for a local sequence, based on the sequence
+ * configuration.
+ */
+int64
+seq_local_nextval(Relation rel, int64 incby, int64 maxv,
+				  int64 minv, int64 cache, bool cycle,
+				  int64 *last)
+{
+	int64		result;
+	int64		fetch;
+	int64		next;
+	int64		rescnt = 0;
+	int64		log;
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_seq_local_data seq;
+	Page		page;
+	bool		logit = false;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+	page = BufferGetPage(buf);
+
+	*last = next = result = seq->last_value;
+	fetch = cache;
+	log = seq->log_cnt;
+
+	if (!seq->is_called)
+	{
+		rescnt++;				/* return last_value if not is_called */
+		fetch--;
+	}
+
+	/*
+	 * Decide whether we should emit a WAL log record.  If so, force up the
+	 * fetch count to grab SEQ_LOCAL_LOG_VALS more values than we actually
+	 * need to cache.  (These will then be usable without logging.)
+	 *
+	 * If this is the first nextval after a checkpoint, we must force a new
+	 * WAL record to be written anyway, else replay starting from the
+	 * checkpoint would fail to advance the sequence past the logged values.
+	 * In this case we may as well fetch extra values.
+	 */
+	if (log < fetch || !seq->is_called)
+	{
+		/* forced log to satisfy local demand for values */
+		fetch = log = fetch + SEQ_LOCAL_LOG_VALS;
+		logit = true;
+	}
+	else
+	{
+		XLogRecPtr	redoptr = GetRedoRecPtr();
+
+		if (PageGetLSN(page) <= redoptr)
+		{
+			/* last update of seq was before checkpoint */
+			fetch = log = fetch + SEQ_LOCAL_LOG_VALS;
+			logit = true;
+		}
+	}
+
+	while (fetch)				/* try to fetch cache [+ log ] numbers */
+	{
+		/*
+		 * Check MAXVALUE for ascending sequences and MINVALUE for descending
+		 * sequences
+		 */
+		if (incby > 0)
+		{
+			/* ascending sequence */
+			if ((maxv >= 0 && next > maxv - incby) ||
+				(maxv < 0 && next + incby > maxv))
+			{
+				if (rescnt > 0)
+					break;		/* stop fetching */
+				if (!cycle)
+					ereport(ERROR,
+							(errcode(ERRCODE_SEQUENCE_GENERATOR_LIMIT_EXCEEDED),
+							 errmsg("nextval: reached maximum value of sequence \"%s\" (%" PRId64 ")",
+									RelationGetRelationName(rel),
+									maxv)));
+				next = minv;
+			}
+			else
+				next += incby;
+		}
+		else
+		{
+			/* descending sequence */
+			if ((minv < 0 && next < minv - incby) ||
+				(minv >= 0 && next + incby < minv))
+			{
+				if (rescnt > 0)
+					break;		/* stop fetching */
+				if (!cycle)
+					ereport(ERROR,
+							(errcode(ERRCODE_SEQUENCE_GENERATOR_LIMIT_EXCEEDED),
+							 errmsg("nextval: reached minimum value of sequence \"%s\" (%" PRId64 ")",
+									RelationGetRelationName(rel),
+									minv)));
+				next = maxv;
+			}
+			else
+				next += incby;
+		}
+		fetch--;
+		if (rescnt < cache)
+		{
+			log--;
+			rescnt++;
+			*last = next;
+			if (rescnt == 1)	/* if it's first result - */
+				result = next;	/* it's what to return */
+		}
+	}
+
+	log -= fetch;				/* adjust for any unfetched numbers */
+	Assert(log >= 0);
+
+	/*
+	 * If something needs to be WAL logged, acquire an xid, so this
+	 * transaction's commit will trigger a WAL flush and wait for syncrep.
+	 * It's sufficient to ensure the toplevel transaction has an xid, no need
+	 * to assign xids subxacts, that'll already trigger an appropriate wait.
+	 * (Have to do that here, so we're outside the critical section)
+	 */
+	if (logit && RelationNeedsWAL(rel))
+		GetTopTransactionId();
+
+	/* ready to change the on-disk (or really, in-buffer) tuple */
+	START_CRIT_SECTION();
+
+	/*
+	 * We must mark the buffer dirty before doing XLogInsert(); see notes in
+	 * SyncOneBuffer().  However, we don't apply the desired changes just yet.
+	 * This looks like a violation of the buffer update protocol, but it is in
+	 * fact safe because we hold exclusive lock on the buffer.  Any other
+	 * process, including a checkpoint, that tries to examine the buffer
+	 * contents will block until we release the lock, and then will see the
+	 * final state that we install below.
+	 */
+	MarkBufferDirty(buf);
+
+	/* XLOG stuff */
+	if (logit && RelationNeedsWAL(rel))
+	{
+		xl_seq_local_rec xlrec;
+		XLogRecPtr	recptr;
+
+		/*
+		 * We don't log the current state of the tuple, but rather the state
+		 * as it would appear after "log" more fetches.  This lets us skip
+		 * that many future WAL records, at the cost that we lose those
+		 * sequence values if we crash.
+		 */
+		XLogBeginInsert();
+		XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
+
+		/* set values that will be saved in xlog */
+		seq->last_value = next;
+		seq->is_called = true;
+		seq->log_cnt = 0;
+
+		xlrec.locator = rel->rd_locator;
+
+		XLogRegisterData(&xlrec, sizeof(xl_seq_local_rec));
+		XLogRegisterData(seqdatatuple.t_data, seqdatatuple.t_len);
+
+		recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG);
+
+		PageSetLSN(page, recptr);
+	}
+
+	/* Now update sequence tuple to the intended final state */
+	seq->last_value = *last;	/* last fetched number */
+	seq->is_called = true;
+	seq->log_cnt = log;			/* how much is logged */
+
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buf);
+
+	return result;
+}
+
+/*
+ * seq_local_get_table_am()
+ *
+ * Return the table access method used by this sequence.
+ */
+const char *
+seq_local_get_table_am(void)
+{
+	return DEFAULT_TABLE_ACCESS_METHOD;
+}
+
+/*
+ * seq_local_init()
+ *
+ * Add the sequence attributes to the relation created for this sequence
+ * AM and insert a tuple of metadata into the sequence relation, based on
+ * the information guessed from pg_sequences.  This is the first tuple
+ * inserted after the relation has been created, filling in its heap
+ * table.
+ */
+void
+seq_local_init(Relation rel, int64 last_value, bool is_called)
+{
+	Datum		value[SEQ_LOCAL_COL_LASTCOL];
+	bool		null[SEQ_LOCAL_COL_LASTCOL];
+	List	   *elts = NIL;
+	List	   *atcmds = NIL;
+	ListCell   *lc;
+	TupleDesc	tupdesc;
+	HeapTuple	tuple;
+
+	/*
+	 * Create relation (and fill value[] and null[] for the initial tuple).
+	 */
+	for (int i = SEQ_LOCAL_COL_FIRSTCOL; i <= SEQ_LOCAL_COL_LASTCOL; i++)
+	{
+		ColumnDef  *coldef = NULL;
+
+		switch (i)
+		{
+			case SEQ_LOCAL_COL_LASTVAL:
+				coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
+				value[i - 1] = Int64GetDatumFast(last_value);
+				break;
+			case SEQ_LOCAL_COL_LOG:
+				coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
+				value[i - 1] = Int64GetDatum(0);
+				break;
+			case SEQ_LOCAL_COL_CALLED:
+				coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid);
+				value[i - 1] = BoolGetDatum(is_called);
+				break;
+		}
+
+		coldef->is_not_null = true;
+		null[i - 1] = false;
+		elts = lappend(elts, coldef);
+	}
+
+	/* Add all the attributes to the sequence */
+	foreach(lc, elts)
+	{
+		AlterTableCmd *atcmd;
+
+		atcmd = makeNode(AlterTableCmd);
+		atcmd->subtype = AT_AddColumnToSequence;
+		atcmd->def = (Node *) lfirst(lc);
+		atcmds = lappend(atcmds, atcmd);
+	}
+
+	/*
+	 * No recursion needed.  Note that EventTriggerAlterTableStart() should
+	 * have been called.
+	 */
+	AlterTableInternal(RelationGetRelid(rel), atcmds, false);
+	CommandCounterIncrement();
+
+	tupdesc = RelationGetDescr(rel);
+	tuple = heap_form_tuple(tupdesc, value, null);
+	fill_seq_with_data(rel, tuple);
+}
+
+/*
+ * seq_local_setval()
+ *
+ * Callback for setval().
+ */
+void
+seq_local_setval(Relation rel, int64 next, bool iscalled)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_seq_local_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	/* ready to change the on-disk (or really, in-buffer) tuple */
+	START_CRIT_SECTION();
+	seq->last_value = next;		/* last fetched number */
+	seq->is_called = iscalled;
+	seq->log_cnt = 0;
+
+	MarkBufferDirty(buf);
+
+	/* XLOG stuff */
+	if (RelationNeedsWAL(rel))
+	{
+		xl_seq_local_rec xlrec;
+		XLogRecPtr	recptr;
+		Page		page = BufferGetPage(buf);
+
+		XLogBeginInsert();
+		XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
+
+		xlrec.locator = rel->rd_locator;
+		XLogRegisterData(&xlrec, sizeof(xl_seq_local_rec));
+		XLogRegisterData(seqdatatuple.t_data, seqdatatuple.t_len);
+
+		recptr = XLogInsert(RM_SEQ_LOCAL_ID, XLOG_SEQ_LOCAL_LOG);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buf);
+}
+
+/*
+ * seq_local_reset()
+ *
+ * Perform a hard reset on the local sequence, rewriting its heap data
+ * entirely.
+ */
+void
+seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state)
+{
+	Form_pg_seq_local_data seq;
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	HeapTuple	tuple;
+
+	/* lock buffer page and read tuple */
+	(void) read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	/*
+	 * Copy the existing sequence tuple.
+	 */
+	tuple = heap_copytuple(&seqdatatuple);
+
+	/* Now we're done with the old page */
+	UnlockReleaseBuffer(buf);
+
+	/*
+	 * Modify the copied tuple to execute the restart (compare the RESTART
+	 * action in AlterSequence)
+	 */
+	seq = (Form_pg_seq_local_data) GETSTRUCT(tuple);
+	seq->last_value = startv;
+	seq->is_called = is_called;
+	if (reset_state)
+		seq->log_cnt = 0;
+
+	/*
+	 * Create a new storage file for the sequence.
+	 */
+	RelationSetNewRelfilenumber(rel, rel->rd_rel->relpersistence);
+
+	/*
+	 * Ensure sequence's relfrozenxid is at 0, since it won't contain any
+	 * unfrozen XIDs.  Same with relminmxid, since a sequence will never
+	 * contain multixacts.
+	 */
+	Assert(rel->rd_rel->relfrozenxid == InvalidTransactionId);
+	Assert(rel->rd_rel->relminmxid == InvalidMultiXactId);
+
+	/*
+	 * Insert the modified tuple into the new storage file.
+	 */
+	fill_seq_with_data(rel, tuple);
+}
+
+/*
+ * seq_local_get_state()
+ *
+ * Retrieve the state of a local sequence.
+ */
+void
+seq_local_get_state(Relation rel,
+					int64 *last_value,
+					bool *is_called,
+					XLogRecPtr *page_lsn)
+{
+	Buffer		buf;
+	Page		page;
+	HeapTupleData seqdatatuple;
+	Form_pg_seq_local_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+	page = BufferGetPage(buf);
+
+	*last_value = seq->last_value;
+	*is_called = seq->is_called;
+	*page_lsn = PageGetLSN(page);
+
+	UnlockReleaseBuffer(buf);
+}
+
+/*
+ * seq_local_change_persistence()
+ *
+ * Persistence change for the local sequence Relation.
+ */
+void
+seq_local_change_persistence(Relation rel, char newrelpersistence)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+
+	(void) read_seq_tuple(rel, &buf, &seqdatatuple);
+	RelationSetNewRelfilenumber(rel, newrelpersistence);
+	fill_seq_with_data(rel, &seqdatatuple);
+	UnlockReleaseBuffer(buf);
+}
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 4fda03a3cfcc..1892b6f2754d 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -29,11 +29,11 @@
 #include "access/heapam_xlog.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
+#include "access/seqlocal_xlog.h"
 #include "access/spgxlog.h"
 #include "access/xact.h"
 #include "catalog/storage_xlog.h"
 #include "commands/dbcommands_xlog.h"
-#include "commands/sequence_xlog.h"
 #include "commands/tablespace.h"
 #include "replication/decode.h"
 #include "replication/message.h"
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 64cb6278409f..f99acfd2b4bb 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -53,7 +53,6 @@ OBJS = \
 	schemacmds.o \
 	seclabel.o \
 	sequence.o \
-	sequence_xlog.o \
 	statscmds.o \
 	subscriptioncmds.o \
 	tablecmds.o \
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index ca3f53c62135..be86ec9d1993 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -41,7 +41,6 @@ backend_sources += files(
   'schemacmds.c',
   'seclabel.c',
   'sequence.c',
-  'sequence_xlog.c',
   'statscmds.c',
   'subscriptioncmds.c',
   'tablecmds.c',
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 4c5135c388dd..ea90db9ddd7b 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -17,6 +17,7 @@
 #include "access/htup_details.h"
 #include "access/multixact.h"
 #include "access/relation.h"
+#include "access/seqlocalam.h"
 #include "access/sequence.h"
 #include "access/table.h"
 #include "access/transam.h"
@@ -31,7 +32,6 @@
 #include "catalog/storage_xlog.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
-#include "commands/sequence_xlog.h"
 #include "commands/tablecmds.h"
 #include "funcapi.h"
 #include "miscadmin.h"
@@ -50,13 +50,6 @@
 #include "utils/varlena.h"
 
 
-/*
- * We don't want to log each fetching of a value from a sequence,
- * so we pre-log a few fetches in advance. In the event of
- * crash we can lose (skip over) as many values as we pre-logged.
- */
-#define SEQ_LOG_VALS	32
-
 /*
  * We store a SeqTable item for every sequence we have touched in the current
  * session.  This is needed to hold onto nextval/currval state.  (We can't
@@ -86,13 +79,9 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
  */
 static SeqTableData *last_used_seq = NULL;
 
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
-static void fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum);
 static Relation lock_and_open_sequence(SeqTable seq);
 static void create_seq_hashtable(void);
 static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
-static Form_pg_sequence_data read_seq_tuple(Relation rel,
-											Buffer *buf, HeapTuple seqdatatuple);
 static void init_params(ParseState *pstate, List *options, bool for_identity,
 						bool isInit,
 						Form_pg_sequence seqform,
@@ -123,14 +112,8 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	Relation	rel;
 	HeapTuple	tuple;
 	TupleDesc	tupDesc;
-	Datum		value[SEQ_COL_LASTCOL];
-	bool		null[SEQ_COL_LASTCOL];
-	List	   *elts = NIL;
-	List	   *atcmds = NIL;
-	ListCell   *lc;
 	Datum		pgs_values[Natts_pg_sequence];
 	bool		pgs_nulls[Natts_pg_sequence];
-	int			i;
 
 	/*
 	 * If if_not_exists was given and a relation with the same name already
@@ -163,35 +146,6 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 				&seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
-	/*
-	 * Create relation (and fill value[] and null[] for the tuple)
-	 */
-	for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++)
-	{
-		ColumnDef  *coldef = NULL;
-
-		switch (i)
-		{
-			case SEQ_COL_LASTVAL:
-				coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
-				value[i - 1] = Int64GetDatumFast(last_value);
-				break;
-			case SEQ_COL_LOG:
-				coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
-				value[i - 1] = Int64GetDatum((int64) 0);
-				break;
-			case SEQ_COL_CALLED:
-				coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid);
-				value[i - 1] = BoolGetDatum(false);
-				break;
-		}
-
-		coldef->is_not_null = true;
-		null[i - 1] = false;
-
-		elts = lappend(elts, coldef);
-	}
-
 	stmt->relation = seq->sequence;
 	stmt->inhRelations = NIL;
 	stmt->constraints = NIL;
@@ -212,29 +166,8 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	rel = sequence_open(seqoid, AccessExclusiveLock);
 
-	/* Add all the attributes to the sequence */
-	foreach(lc, elts)
-	{
-		AlterTableCmd *atcmd;
-
-		atcmd = makeNode(AlterTableCmd);
-		atcmd->subtype = AT_AddColumnToSequence;
-		atcmd->def = (Node *) lfirst(lc);
-		atcmds = lappend(atcmds, atcmd);
-	}
-
-	/*
-	 * No recursion needed.  Note that EventTriggerAlterTableStart() should
-	 * have been called.
-	 */
-	AlterTableInternal(RelationGetRelid(rel), atcmds, false);
-	CommandCounterIncrement();
-
-	tupDesc = RelationGetDescr(rel);
-
-	/* now initialize the sequence's data */
-	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	/* now initialize the sequence table structure and its data */
+	seq_local_init(rel, last_value, is_called);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -283,10 +216,6 @@ ResetSequence(Oid seq_relid)
 {
 	Relation	seq_rel;
 	SeqTable	elm;
-	Form_pg_sequence_data seq;
-	Buffer		buf;
-	HeapTupleData seqdatatuple;
-	HeapTuple	tuple;
 	HeapTuple	pgstuple;
 	Form_pg_sequence pgsform;
 	int64		startv;
@@ -297,7 +226,6 @@ ResetSequence(Oid seq_relid)
 	 * indeed a sequence.
 	 */
 	init_sequence(seq_relid, &elm, &seq_rel);
-	(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
 
 	pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
 	if (!HeapTupleIsValid(pgstuple))
@@ -306,40 +234,8 @@ ResetSequence(Oid seq_relid)
 	startv = pgsform->seqstart;
 	ReleaseSysCache(pgstuple);
 
-	/*
-	 * Copy the existing sequence tuple.
-	 */
-	tuple = heap_copytuple(&seqdatatuple);
-
-	/* Now we're done with the old page */
-	UnlockReleaseBuffer(buf);
-
-	/*
-	 * Modify the copied tuple to execute the restart (compare the RESTART
-	 * action in AlterSequence)
-	 */
-	seq = (Form_pg_sequence_data) GETSTRUCT(tuple);
-	seq->last_value = startv;
-	seq->is_called = false;
-	seq->log_cnt = 0;
-
-	/*
-	 * Create a new storage file for the sequence.
-	 */
-	RelationSetNewRelfilenumber(seq_rel, seq_rel->rd_rel->relpersistence);
-
-	/*
-	 * Ensure sequence's relfrozenxid is at 0, since it won't contain any
-	 * unfrozen XIDs.  Same with relminmxid, since a sequence will never
-	 * contain multixacts.
-	 */
-	Assert(seq_rel->rd_rel->relfrozenxid == InvalidTransactionId);
-	Assert(seq_rel->rd_rel->relminmxid == InvalidMultiXactId);
-
-	/*
-	 * Insert the modified tuple into the new storage file.
-	 */
-	fill_seq_with_data(seq_rel, tuple);
+	/* Sequence state is forcibly reset here. */
+	seq_local_reset(seq_rel, startv, false, true);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -348,105 +244,6 @@ ResetSequence(Oid seq_relid)
 	sequence_close(seq_rel, NoLock);
 }
 
-/*
- * Initialize a sequence's relation with the specified tuple as content
- *
- * This handles unlogged sequences by writing to both the main and the init
- * fork as necessary.
- */
-static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
-{
-	fill_seq_fork_with_data(rel, tuple, MAIN_FORKNUM);
-
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
-	{
-		SMgrRelation srel;
-
-		srel = smgropen(rel->rd_locator, INVALID_PROC_NUMBER);
-		smgrcreate(srel, INIT_FORKNUM, false);
-		log_smgrcreate(&rel->rd_locator, INIT_FORKNUM);
-		fill_seq_fork_with_data(rel, tuple, INIT_FORKNUM);
-		FlushRelationBuffers(rel);
-		smgrclose(srel);
-	}
-}
-
-/*
- * Initialize a sequence's relation fork with the specified tuple as content
- */
-static void
-fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum)
-{
-	Buffer		buf;
-	Page		page;
-	sequence_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(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(page);
-	sm->magic = SEQ_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);
-
-	/* check the comment above nextval_internal()'s equivalent call. */
-	if (RelationNeedsWAL(rel))
-		GetTopTransactionId();
-
-	START_CRIT_SECTION();
-
-	MarkBufferDirty(buf);
-
-	offnum = PageAddItem(page, tuple->t_data, tuple->t_len, InvalidOffsetNumber, false, false);
-	if (offnum != FirstOffsetNumber)
-		elog(ERROR, "failed to add sequence tuple to page");
-
-	/* XLOG stuff */
-	if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM)
-	{
-		xl_seq_rec	xlrec;
-		XLogRecPtr	recptr;
-
-		XLogBeginInsert();
-		XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
-
-		xlrec.locator = rel->rd_locator;
-
-		XLogRegisterData(&xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData(tuple->t_data, tuple->t_len);
-
-		recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	UnlockReleaseBuffer(buf);
-}
-
 /*
  * AlterSequence
  *
@@ -458,10 +255,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	Oid			relid;
 	SeqTable	elm;
 	Relation	seqrel;
-	Buffer		buf;
-	HeapTupleData datatuple;
 	Form_pg_sequence seqform;
-	Form_pg_sequence_data newdataform;
 	bool		need_seq_rewrite;
 	List	   *owned_by;
 	ObjectAddress address;
@@ -470,7 +264,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	bool		reset_state = false;
 	bool		is_called;
 	int64		last_value;
-	HeapTuple	newdatatuple;
+	XLogRecPtr	page_lsn;
 
 	/* Open and lock sequence, and check for ownership along the way. */
 	relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -497,16 +291,8 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 
 	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
 
-	/* lock page buffer and read tuple into new sequence structure */
-	(void) read_seq_tuple(seqrel, &buf, &datatuple);
-
-	/* copy the existing sequence data tuple, so it can be modified locally */
-	newdatatuple = heap_copytuple(&datatuple);
-	newdataform = (Form_pg_sequence_data) GETSTRUCT(newdatatuple);
-	last_value = newdataform->last_value;
-	is_called = newdataform->is_called;
-
-	UnlockReleaseBuffer(buf);
+	/* Read sequence data */
+	seq_local_get_state(seqrel, &last_value, &is_called, &page_lsn);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -516,32 +302,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	/* If needed, rewrite the sequence relation itself */
 	if (need_seq_rewrite)
 	{
-		/* check the comment above nextval_internal()'s equivalent call. */
 		if (RelationNeedsWAL(seqrel))
 			GetTopTransactionId();
 
-		/*
-		 * Create a new storage file for the sequence, making the state
-		 * changes transactional.
-		 */
-		RelationSetNewRelfilenumber(seqrel, seqrel->rd_rel->relpersistence);
-
-		/*
-		 * Ensure sequence's relfrozenxid is at 0, since it won't contain any
-		 * unfrozen XIDs.  Same with relminmxid, since a sequence will never
-		 * contain multixacts.
-		 */
-		Assert(seqrel->rd_rel->relfrozenxid == InvalidTransactionId);
-		Assert(seqrel->rd_rel->relminmxid == InvalidMultiXactId);
-
-		/*
-		 * Insert the modified tuple into the new storage file.
-		 */
-		newdataform->last_value = last_value;
-		newdataform->is_called = is_called;
-		if (reset_state)
-			newdataform->log_cnt = 0;
-		fill_seq_with_data(seqrel, newdatatuple);
+		seq_local_reset(seqrel, last_value, is_called, reset_state);
 	}
 
 	/* Clear local cache so that we don't think we have cached numbers */
@@ -570,8 +334,6 @@ SequenceChangePersistence(Oid relid, char newrelpersistence)
 {
 	SeqTable	elm;
 	Relation	seqrel;
-	Buffer		buf;
-	HeapTupleData seqdatatuple;
 
 	/*
 	 * ALTER SEQUENCE acquires this lock earlier.  If we're processing an
@@ -586,10 +348,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence)
 	if (RelationNeedsWAL(seqrel))
 		GetTopTransactionId();
 
-	(void) read_seq_tuple(seqrel, &buf, &seqdatatuple);
-	RelationSetNewRelfilenumber(seqrel, newrelpersistence);
-	fill_seq_with_data(seqrel, &seqdatatuple);
-	UnlockReleaseBuffer(buf);
+	seq_local_change_persistence(seqrel, newrelpersistence);
 
 	sequence_close(seqrel, NoLock);
 }
@@ -652,24 +411,15 @@ nextval_internal(Oid relid, bool check_permissions)
 {
 	SeqTable	elm;
 	Relation	seqrel;
-	Buffer		buf;
-	Page		page;
 	HeapTuple	pgstuple;
 	Form_pg_sequence pgsform;
-	HeapTupleData seqdatatuple;
-	Form_pg_sequence_data seq;
 	int64		incby,
 				maxv,
 				minv,
 				cache,
-				log,
-				fetch,
 				last;
-	int64		result,
-				next,
-				rescnt = 0;
+	int64		result;
 	bool		cycle;
-	bool		logit = false;
 
 	/* open and lock sequence */
 	init_sequence(relid, &elm, &seqrel);
@@ -714,105 +464,9 @@ nextval_internal(Oid relid, bool check_permissions)
 	cycle = pgsform->seqcycle;
 	ReleaseSysCache(pgstuple);
 
-	/* lock page buffer and read tuple */
-	seq = read_seq_tuple(seqrel, &buf, &seqdatatuple);
-	page = BufferGetPage(buf);
-
-	last = next = result = seq->last_value;
-	fetch = cache;
-	log = seq->log_cnt;
-
-	if (!seq->is_called)
-	{
-		rescnt++;				/* return last_value if not is_called */
-		fetch--;
-	}
-
-	/*
-	 * Decide whether we should emit a WAL log record.  If so, force up the
-	 * fetch count to grab SEQ_LOG_VALS more values than we actually need to
-	 * cache.  (These will then be usable without logging.)
-	 *
-	 * If this is the first nextval after a checkpoint, we must force a new
-	 * WAL record to be written anyway, else replay starting from the
-	 * checkpoint would fail to advance the sequence past the logged values.
-	 * In this case we may as well fetch extra values.
-	 */
-	if (log < fetch || !seq->is_called)
-	{
-		/* forced log to satisfy local demand for values */
-		fetch = log = fetch + SEQ_LOG_VALS;
-		logit = true;
-	}
-	else
-	{
-		XLogRecPtr	redoptr = GetRedoRecPtr();
-
-		if (PageGetLSN(page) <= redoptr)
-		{
-			/* last update of seq was before checkpoint */
-			fetch = log = fetch + SEQ_LOG_VALS;
-			logit = true;
-		}
-	}
-
-	while (fetch)				/* try to fetch cache [+ log ] numbers */
-	{
-		/*
-		 * Check MAXVALUE for ascending sequences and MINVALUE for descending
-		 * sequences
-		 */
-		if (incby > 0)
-		{
-			/* ascending sequence */
-			if ((maxv >= 0 && next > maxv - incby) ||
-				(maxv < 0 && next + incby > maxv))
-			{
-				if (rescnt > 0)
-					break;		/* stop fetching */
-				if (!cycle)
-					ereport(ERROR,
-							(errcode(ERRCODE_SEQUENCE_GENERATOR_LIMIT_EXCEEDED),
-							 errmsg("nextval: reached maximum value of sequence \"%s\" (%" PRId64 ")",
-									RelationGetRelationName(seqrel),
-									maxv)));
-				next = minv;
-			}
-			else
-				next += incby;
-		}
-		else
-		{
-			/* descending sequence */
-			if ((minv < 0 && next < minv - incby) ||
-				(minv >= 0 && next + incby < minv))
-			{
-				if (rescnt > 0)
-					break;		/* stop fetching */
-				if (!cycle)
-					ereport(ERROR,
-							(errcode(ERRCODE_SEQUENCE_GENERATOR_LIMIT_EXCEEDED),
-							 errmsg("nextval: reached minimum value of sequence \"%s\" (%" PRId64 ")",
-									RelationGetRelationName(seqrel),
-									minv)));
-				next = maxv;
-			}
-			else
-				next += incby;
-		}
-		fetch--;
-		if (rescnt < cache)
-		{
-			log--;
-			rescnt++;
-			last = next;
-			if (rescnt == 1)	/* if it's first result - */
-				result = next;	/* it's what to return */
-		}
-	}
-
-	log -= fetch;				/* adjust for any unfetched numbers */
-	Assert(log >= 0);
+	/* retrieve next value from the access method */
+	result = seq_local_nextval(seqrel, incby, maxv, minv, cache, cycle,
+							   &last);
 
 	/* save info in local cache */
 	elm->increment = incby;
@@ -822,69 +476,6 @@ nextval_internal(Oid relid, bool check_permissions)
 
 	last_used_seq = elm;
 
-	/*
-	 * If something needs to be WAL logged, acquire an xid, so this
-	 * transaction's commit will trigger a WAL flush and wait for syncrep.
-	 * It's sufficient to ensure the toplevel transaction has an xid, no need
-	 * to assign xids subxacts, that'll already trigger an appropriate wait.
-	 * (Have to do that here, so we're outside the critical section)
-	 */
-	if (logit && RelationNeedsWAL(seqrel))
-		GetTopTransactionId();
-
-	/* ready to change the on-disk (or really, in-buffer) tuple */
-	START_CRIT_SECTION();
-
-	/*
-	 * We must mark the buffer dirty before doing XLogInsert(); see notes in
-	 * SyncOneBuffer().  However, we don't apply the desired changes just yet.
-	 * This looks like a violation of the buffer update protocol, but it is in
-	 * fact safe because we hold exclusive lock on the buffer.  Any other
-	 * process, including a checkpoint, that tries to examine the buffer
-	 * contents will block until we release the lock, and then will see the
-	 * final state that we install below.
-	 */
-	MarkBufferDirty(buf);
-
-	/* XLOG stuff */
-	if (logit && RelationNeedsWAL(seqrel))
-	{
-		xl_seq_rec	xlrec;
-		XLogRecPtr	recptr;
-
-		/*
-		 * We don't log the current state of the tuple, but rather the state
-		 * as it would appear after "log" more fetches.  This lets us skip
-		 * that many future WAL records, at the cost that we lose those
-		 * sequence values if we crash.
-		 */
-		XLogBeginInsert();
-		XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
-
-		/* set values that will be saved in xlog */
-		seq->last_value = next;
-		seq->is_called = true;
-		seq->log_cnt = 0;
-
-		xlrec.locator = seqrel->rd_locator;
-
-		XLogRegisterData(&xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData(seqdatatuple.t_data, seqdatatuple.t_len);
-
-		recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG);
-
-		PageSetLSN(page, recptr);
-	}
-
-	/* Now update sequence tuple to the intended final state */
-	seq->last_value = last;		/* last fetched number */
-	seq->is_called = true;
-	seq->log_cnt = log;			/* how much is logged */
-
-	END_CRIT_SECTION();
-
-	UnlockReleaseBuffer(buf);
-
 	sequence_close(seqrel, NoLock);
 
 	return result;
@@ -974,9 +565,6 @@ SetSequence(Oid relid, int64 next, bool iscalled)
 {
 	SeqTable	elm;
 	Relation	seqrel;
-	Buffer		buf;
-	HeapTupleData seqdatatuple;
-	Form_pg_sequence_data seq;
 	HeapTuple	pgstuple;
 	Form_pg_sequence pgsform;
 	int64		maxv,
@@ -1010,9 +598,6 @@ SetSequence(Oid relid, int64 next, bool iscalled)
 	 */
 	PreventCommandIfParallelMode("setval()");
 
-	/* lock page buffer and read tuple */
-	seq = read_seq_tuple(seqrel, &buf, &seqdatatuple);
-
 	if ((next < minv) || (next > maxv))
 		ereport(ERROR,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
@@ -1034,37 +619,8 @@ SetSequence(Oid relid, int64 next, bool iscalled)
 	if (RelationNeedsWAL(seqrel))
 		GetTopTransactionId();
 
-	/* ready to change the on-disk (or really, in-buffer) tuple */
-	START_CRIT_SECTION();
-
-	seq->last_value = next;		/* last fetched number */
-	seq->is_called = iscalled;
-	seq->log_cnt = 0;
-
-	MarkBufferDirty(buf);
-
-	/* XLOG stuff */
-	if (RelationNeedsWAL(seqrel))
-	{
-		xl_seq_rec	xlrec;
-		XLogRecPtr	recptr;
-		Page		page = BufferGetPage(buf);
-
-		XLogBeginInsert();
-		XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
-
-		xlrec.locator = seqrel->rd_locator;
-		XLogRegisterData(&xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData(seqdatatuple.t_data, seqdatatuple.t_len);
-
-		recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	UnlockReleaseBuffer(buf);
+	/* Call the access method callback */
+	seq_local_setval(seqrel, next, iscalled);
 
 	sequence_close(seqrel, NoLock);
 }
@@ -1205,62 +761,6 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 }
 
 
-/*
- * Given an opened sequence relation, lock the page buffer and find the tuple
- *
- * *buf receives the reference to the pinned-and-ex-locked buffer
- * *seqdatatuple receives the reference to the sequence tuple proper
- *		(this arg should point to a local variable of type HeapTupleData)
- *
- * Function's return value points to the data payload of the tuple
- */
-static Form_pg_sequence_data
-read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
-{
-	Page		page;
-	ItemId		lp;
-	sequence_magic *sm;
-	Form_pg_sequence_data seq;
-
-	*buf = ReadBuffer(rel, 0);
-	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
-
-	page = BufferGetPage(*buf);
-	sm = (sequence_magic *) PageGetSpecialPointer(page);
-
-	if (sm->magic != SEQ_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);
-
-	/*
-	 * Previous releases of Postgres neglected to prevent SELECT FOR UPDATE on
-	 * a sequence, which would leave a non-frozen XID in the sequence tuple's
-	 * xmax, which eventually leads to clog access failures or worse. If we
-	 * see this has happened, clean up after it.  We treat this like a hint
-	 * bit update, ie, don't bother to WAL-log it, since we can certainly do
-	 * this again if the update gets lost.
-	 */
-	Assert(!(seqdatatuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI));
-	if (HeapTupleHeaderGetRawXmax(seqdatatuple->t_data) != InvalidTransactionId)
-	{
-		HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId);
-		seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED;
-		seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID;
-		MarkBufferDirtyHint(*buf, true);
-	}
-
-	seq = (Form_pg_sequence_data) GETSTRUCT(seqdatatuple);
-
-	return seq;
-}
-
 /*
  * init_params: process the options list of CREATE or ALTER SEQUENCE, and
  * store the values into appropriate fields of seqform, for changes that go
@@ -1848,19 +1348,16 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		Buffer		buf;
-		HeapTupleData seqtuple;
-		Form_pg_sequence_data seq;
-		Page		page;
+		bool		is_called;
+		int64		last_value;
+		XLogRecPtr	page_lsn;
 
-		seq = read_seq_tuple(seqrel, &buf, &seqtuple);
-		page = BufferGetPage(buf);
+		seq_local_get_state(seqrel, &last_value, &is_called,
+							&page_lsn);
 
-		values[0] = Int64GetDatum(seq->last_value);
-		values[1] = BoolGetDatum(seq->is_called);
-		values[2] = LSNGetDatum(PageGetLSN(page));
-
-		UnlockReleaseBuffer(buf);
+		values[0] = Int64GetDatum(last_value);
+		values[1] = BoolGetDatum(is_called);
+		values[2] = LSNGetDatum(page_lsn);
 	}
 	else
 		memset(isnull, true, sizeof(isnull));
@@ -1887,6 +1384,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	Relation	seqrel;
 	bool		is_called = false;
 	int64		result = 0;
+	XLogRecPtr	page_lsn = InvalidXLogRecPtr;
 
 	/* open and lock sequence */
 	init_sequence(relid, &elm, &seqrel);
@@ -1904,17 +1402,9 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		Buffer		buf;
-		HeapTupleData seqtuple;
-		Form_pg_sequence_data seq;
-
-		seq = read_seq_tuple(seqrel, &buf, &seqtuple);
-
-		is_called = seq->is_called;
-		result = seq->last_value;
-
-		UnlockReleaseBuffer(buf);
+		seq_local_get_state(seqrel, &result, &is_called, &page_lsn);
 	}
+
 	sequence_close(seqrel, NoLock);
 
 	if (is_called)
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c767e..6709e87914d7 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -16,7 +16,7 @@
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
+/seqlocaldesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index 931ab8b979e2..770a56fd3b37 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -19,12 +19,12 @@
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
+#include "access/seqlocal_xlog.h"
 #include "access/spgxlog.h"
 #include "access/xact.h"
 #include "access/xlog_internal.h"
 #include "catalog/storage_xlog.h"
 #include "commands/dbcommands_xlog.h"
-#include "commands/sequence_xlog.h"
 #include "commands/tablespace.h"
 #include "replication/message.h"
 #include "replication/origin.h"
diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index 5db5d20136f3..06e008756702 100644
--- a/src/bin/pg_waldump/t/001_basic.pl
+++ b/src/bin/pg_waldump/t/001_basic.pl
@@ -67,7 +67,7 @@ Btree
 Hash
 Gin
 Gist
-Sequence
+SequenceLocal
 SPGist
 BRIN
 CommitTs
-- 
2.51.0



  [text/x-diff] v28-0003-Sequence-access-methods-backend-support.patch (65.3K, 4-v28-0003-Sequence-access-methods-backend-support.patch)
  download | inline diff:
From 44269598ef7d9fa334d953bba558c7c0cb7c7398 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
Date: Wed, 7 Jan 2026 14:23:25 +0900
Subject: [PATCH v28 3/7] Sequence access methods - backend support

The "seqlocal" sequence AM is now plugged in as a handler in the
relcache, and a set of callbacks in sequenceam.h.
---
 src/include/access/seqlocalam.h               |  32 ---
 src/include/access/sequenceam.h               | 184 ++++++++++++++++++
 src/include/catalog/pg_am.dat                 |   3 +
 src/include/catalog/pg_am.h                   |   1 +
 src/include/catalog/pg_class.h                |   6 +
 src/include/catalog/pg_proc.dat               |  13 ++
 src/include/catalog/pg_type.dat               |   6 +
 src/include/commands/defrem.h                 |   1 +
 src/include/commands/sequence.h               |  20 --
 src/include/nodes/meson.build                 |   1 +
 src/include/nodes/parsenodes.h                |   1 +
 src/include/utils/guc_hooks.h                 |   2 +
 src/include/utils/rel.h                       |   5 +
 src/backend/access/sequence/Makefile          |   3 +-
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/seqlocalam.c      |  42 +++-
 src/backend/access/sequence/sequence.c        |   3 +-
 src/backend/access/sequence/sequenceamapi.c   | 146 ++++++++++++++
 src/backend/catalog/heap.c                    |   6 +-
 src/backend/commands/amcmds.c                 |  16 ++
 src/backend/commands/sequence.c               |  28 +--
 src/backend/commands/tablecmds.c              |  17 +-
 src/backend/nodes/Makefile                    |   1 +
 src/backend/nodes/gen_node_support.pl         |   2 +
 src/backend/parser/gram.y                     |  12 +-
 src/backend/parser/parse_utilcmd.c            |   2 +
 src/backend/utils/adt/pseudotypes.c           |   1 +
 src/backend/utils/cache/relcache.c            |  91 +++++++--
 src/backend/utils/misc/guc_parameters.dat     |   8 +
 src/backend/utils/misc/guc_tables.c           |   1 +
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/bin/psql/describe.c                       |   2 +
 src/bin/psql/tab-complete.in.c                |   6 +-
 src/test/regress/expected/create_am.out       |  55 ++++--
 src/test/regress/expected/opr_sanity.out      |  12 ++
 src/test/regress/expected/psql.out            |  96 ++++-----
 src/test/regress/expected/type_sanity.out     |  12 +-
 src/test/regress/sql/create_am.sql            |  24 ++-
 src/test/regress/sql/opr_sanity.sql           |  10 +
 src/test/regress/sql/type_sanity.sql          |  12 +-
 src/tools/pgindent/typedefs.list              |   5 +-
 41 files changed, 701 insertions(+), 189 deletions(-)
 delete mode 100644 src/include/access/seqlocalam.h
 create mode 100644 src/include/access/sequenceam.h
 create mode 100644 src/backend/access/sequence/sequenceamapi.c

diff --git a/src/include/access/seqlocalam.h b/src/include/access/seqlocalam.h
deleted file mode 100644
index 2ce54c2b7789..000000000000
--- a/src/include/access/seqlocalam.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * seqlocalam.h
- *	  Local sequence access method.
- *
- * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * src/include/access/seqlocalam.h
- *
- *-------------------------------------------------------------------------
- */
-#ifndef SEQLOCALAM_H
-#define SEQLOCALAM_H
-
-#include "utils/rel.h"
-
-/* access routines */
-extern int64 seq_local_nextval(Relation rel, int64 incby, int64 maxv,
-							   int64 minv, int64 cache, bool cycle,
-							   int64 *last);
-extern const char *seq_local_get_table_am(void);
-extern void seq_local_init(Relation rel, int64 last_value, bool is_called);
-extern void seq_local_setval(Relation rel, int64 next, bool iscalled);
-extern void seq_local_reset(Relation rel, int64 startv, bool is_called,
-							bool reset_state);
-extern void seq_local_get_state(Relation rel, int64 *last_value,
-								bool *is_called, XLogRecPtr *page_lsn);
-extern void seq_local_change_persistence(Relation rel,
-										 char newrelpersistence);
-
-#endif							/* SEQLOCALAM_H */
diff --git a/src/include/access/sequenceam.h b/src/include/access/sequenceam.h
new file mode 100644
index 000000000000..7122d32930c1
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,184 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/sequenceam.h
+ *
+ * NOTES
+ *		See sequenceam.sgml for higher level documentation.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SEQUENCEAM_H
+#define SEQUENCEAM_H
+
+#include "utils/rel.h"
+
+#define DEFAULT_SEQUENCE_ACCESS_METHOD "seqlocal"
+
+/* GUCs */
+extern PGDLLIMPORT char *default_sequence_access_method;
+
+/*
+ * API struct for a sequence AM.  Note this must be allocated in a
+ * server-lifetime manner, typically as a static const struct, which then gets
+ * returned by FormData_pg_am.amhandler.
+ *
+ * In most cases it's not appropriate to call the callbacks directly, use the
+ * sequence_* wrapper functions instead.
+ *
+ * GetSequenceAmRoutine() asserts that required callbacks are filled in,
+ * remember to update when adding a callback.
+ */
+typedef struct SequenceAmRoutine
+{
+	/* this must be set to T_SequenceAmRoutine */
+	NodeTag		type;
+
+	/*
+	 * Retrieve table access method used by a sequence to store its metadata.
+	 */
+	const char *(*get_table_am) (void);
+
+	/*
+	 * Initialize sequence after creating a sequence Relation in pg_class,
+	 * setting up the sequence for use.  "last_value" and "is_called" are
+	 * guessed from the options set for the sequence in CREATE SEQUENCE, based
+	 * on the configuration of pg_sequence.
+	 */
+	void		(*init) (Relation rel, int64 last_value, bool is_called);
+
+	/*
+	 * Retrieve a result for nextval(), based on the options retrieved from
+	 * the sequence's options in pg_sequence.  "last" is the last value
+	 * calculated stored in the session's local cache, for lastval().
+	 */
+	int64		(*nextval) (Relation rel, int64 incby, int64 maxv,
+							int64 minv, int64 cache, bool cycle,
+							int64 *last);
+
+	/*
+	 * Callback to set the state of a sequence, based on the input arguments
+	 * from setval().
+	 */
+	void		(*setval) (Relation rel, int64 next, bool iscalled);
+
+	/*
+	 * Reset a sequence to its initial value.  "reset_state", if set to true,
+	 * means that the sequence parameters have changed, hence its internal
+	 * state may need to be reset as well.  "startv" and "is_called" are
+	 * values guessed from the configuration of the sequence, based on the
+	 * contents of pg_sequence.
+	 */
+	void		(*reset) (Relation rel, int64 startv, bool is_called,
+						  bool reset_state);
+
+	/*
+	 * Returns the current state of a sequence, returning data for
+	 * pg_sequence_last_value() and related DDLs like ALTER SEQUENCE.
+	 * "last_value" and "is_called" should be assigned to the values retrieved
+	 * from the sequence Relation. "page_lsn" is the LSN of the page used
+	 * by the sequence, can be InvalidXLogRecPtr if unknown.
+	 */
+	void		(*get_state) (Relation rel, int64 *last_value,
+							  bool *is_called, XLogRecPtr *page_lsn);
+
+	/*
+	 * Callback used when switching persistence of a sequence Relation, to
+	 * reset the sequence based on its new persistence "newrelpersistence".
+	 */
+	void		(*change_persistence) (Relation rel, char newrelpersistence);
+
+} SequenceAmRoutine;
+
+
+/* ---------------------------------------------------------------------------
+ * Wrapper functions for each callback.
+ * ---------------------------------------------------------------------------
+ */
+
+/*
+ * Returns the name of the table access method used by this sequence.
+ */
+static inline const char *
+sequence_get_table_am(Relation rel)
+{
+	return rel->rd_sequenceam->get_table_am();
+}
+
+/*
+ * Insert tuple data based on the information guessed from the contents
+ * of pg_sequence.
+ */
+static inline void
+sequence_init(Relation rel, int64 last_value, bool is_called)
+{
+	rel->rd_sequenceam->init(rel, last_value, is_called);
+}
+
+/*
+ * Allocate a set of values for the given sequence.  "last" is the last value
+ * allocated.  The result returned is the next value of the sequence computed.
+ */
+static inline int64
+sequence_nextval(Relation rel, int64 incby, int64 maxv,
+				 int64 minv, int64 cache, bool cycle,
+				 int64 *last)
+{
+	return rel->rd_sequenceam->nextval(rel, incby, maxv, minv, cache,
+									   cycle, last);
+}
+
+/*
+ * Callback to set the state of a sequence, based on the input arguments
+ * from setval().
+ */
+static inline void
+sequence_setval(Relation rel, int64 next, bool iscalled)
+{
+	rel->rd_sequenceam->setval(rel, next, iscalled);
+}
+
+/*
+ * Reset a sequence to its initial state.
+ */
+static inline void
+sequence_reset(Relation rel, int64 startv, bool is_called,
+			   bool reset_state)
+{
+	rel->rd_sequenceam->reset(rel, startv, is_called, reset_state);
+}
+
+/*
+ * Retrieve sequence metadata.
+ */
+static inline void
+sequence_get_state(Relation rel, int64 *last_value, bool *is_called,
+				   XLogRecPtr *page_lsn)
+{
+	rel->rd_sequenceam->get_state(rel, last_value, is_called, page_lsn);
+}
+
+/*
+ * Callback to change the persistence of a sequence Relation.
+ */
+static inline void
+sequence_change_persistence(Relation rel, char newrelpersistence)
+{
+	rel->rd_sequenceam->change_persistence(rel, newrelpersistence);
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions in sequenceamapi.c
+ * ----------------------------------------------------------------------------
+ */
+
+extern const SequenceAmRoutine *GetSequenceAmRoutine(Oid amhandler);
+extern Oid	GetSequenceAmRoutineId(Oid amoid);
+
+#endif							/* SEQUENCEAM_H */
diff --git a/src/include/catalog/pg_am.dat b/src/include/catalog/pg_am.dat
index 46d361047fe6..bb888cdbaff5 100644
--- a/src/include/catalog/pg_am.dat
+++ b/src/include/catalog/pg_am.dat
@@ -15,6 +15,9 @@
 { oid => '2', oid_symbol => 'HEAP_TABLE_AM_OID',
   descr => 'heap table access method',
   amname => 'heap', amhandler => 'heap_tableam_handler', amtype => 't' },
+{ oid => '8051', oid_symbol => 'LOCAL_SEQUENCE_AM_OID',
+  descr => 'local sequence access method',
+  amname => 'seqlocal', amhandler => 'seq_local_sequenceam_handler', amtype => 's' },
 { oid => '403', oid_symbol => 'BTREE_AM_OID',
   descr => 'b-tree index access method',
   amname => 'btree', amhandler => 'bthandler', amtype => 'i' },
diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h
index e039315255c8..24bdb10f0476 100644
--- a/src/include/catalog/pg_am.h
+++ b/src/include/catalog/pg_am.h
@@ -59,6 +59,7 @@ MAKE_SYSCACHE(AMOID, pg_am_oid_index, 4);
  * Allowed values for amtype
  */
 #define AMTYPE_INDEX					'i' /* index access method */
+#define AMTYPE_SEQUENCE					's' /* sequence access method */
 #define AMTYPE_TABLE					't' /* table access method */
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 89ab34c8349f..59d9bbdb8b40 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -231,6 +231,12 @@ MAKE_SYSCACHE(RELNAMENSP, pg_class_relname_nsp_index, 128);
 	 (relkind) == RELKIND_TOASTVALUE || \
 	 (relkind) == RELKIND_MATVIEW)
 
+/*
+ * Relation kinds with a sequence access method (rd_sequenceam).
+ */
+#define RELKIND_HAS_SEQUENCE_AM(relkind) \
+	((relkind) == RELKIND_SEQUENCE)
+
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
 extern int	errdetail_relkind_not_supported(char relkind);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2ac69bf2df55..f22d4526edbd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -913,6 +913,12 @@
   prorettype => 'table_am_handler', proargtypes => 'internal',
   prosrc => 'heap_tableam_handler' },
 
+# Sequence access method handlers
+{ oid => '8209', descr => 'local sequence access method handler',
+  proname => 'seq_local_sequenceam_handler', provolatile => 'v',
+  prorettype => 'sequence_am_handler', proargtypes => 'internal',
+  prosrc => 'seq_local_sequenceam_handler' },
+
 # Index access method handlers
 { oid => '330', descr => 'btree index access method handler',
   proname => 'bthandler', provolatile => 'v', prorettype => 'index_am_handler',
@@ -7930,6 +7936,13 @@
 { oid => '327', descr => 'I/O',
   proname => 'index_am_handler_out', prorettype => 'cstring',
   proargtypes => 'index_am_handler', prosrc => 'index_am_handler_out' },
+{ oid => '8207', descr => 'I/O',
+  proname => 'sequence_am_handler_in', proisstrict => 'f',
+  prorettype => 'sequence_am_handler', proargtypes => 'cstring',
+  prosrc => 'sequence_am_handler_in' },
+{ oid => '8208', descr => 'I/O',
+  proname => 'sequence_am_handler_out', prorettype => 'cstring',
+  proargtypes => 'sequence_am_handler', prosrc => 'sequence_am_handler_out' },
 { oid => '3311', descr => 'I/O',
   proname => 'tsm_handler_in', proisstrict => 'f', prorettype => 'tsm_handler',
   proargtypes => 'cstring', prosrc => 'tsm_handler_in' },
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index a1a753d17978..db10325cb2df 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -632,6 +632,12 @@
   typcategory => 'P', typinput => 'index_am_handler_in',
   typoutput => 'index_am_handler_out', typreceive => '-', typsend => '-',
   typalign => 'i' },
+{ oid => '8210',
+  descr => 'pseudo-type for the result of a sequence AM handler function',
+  typname => 'sequence_am_handler', typlen => '4', typbyval => 't',
+  typtype => 'p', typcategory => 'P', typinput => 'sequence_am_handler_in',
+  typoutput => 'sequence_am_handler_out', typreceive => '-', typsend => '-',
+  typalign => 'i' },
 { oid => '3310',
   descr => 'pseudo-type for the result of a tablesample method function',
   typname => 'tsm_handler', typlen => '4', typbyval => 't', typtype => 'p',
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 8f4a2d9bbc1c..8cb4afc6ce73 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -146,6 +146,7 @@ extern Datum transformGenericOptions(Oid catalogId,
 extern ObjectAddress CreateAccessMethod(CreateAmStmt *stmt);
 extern Oid	get_index_am_oid(const char *amname, bool missing_ok);
 extern Oid	get_table_am_oid(const char *amname, bool missing_ok);
+extern Oid	get_sequence_am_oid(const char *amname, bool missing_ok);
 extern Oid	get_am_oid(const char *amname, bool missing_ok);
 extern char *get_am_name(Oid amOid);
 
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 2c3c4a3f074b..630061760ae1 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -18,26 +18,6 @@
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 
-typedef struct FormData_pg_sequence_data
-{
-	int64		last_value;
-	int64		log_cnt;
-	bool		is_called;
-} FormData_pg_sequence_data;
-
-typedef FormData_pg_sequence_data *Form_pg_sequence_data;
-
-/*
- * Columns of a sequence relation
- */
-
-#define SEQ_COL_LASTVAL			1
-#define SEQ_COL_LOG				2
-#define SEQ_COL_CALLED			3
-
-#define SEQ_COL_FIRSTCOL		SEQ_COL_LASTVAL
-#define SEQ_COL_LASTCOL			SEQ_COL_CALLED
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index 96800215df1b..61bbb0bdce8b 100644
--- a/src/include/nodes/meson.build
+++ b/src/include/nodes/meson.build
@@ -10,6 +10,7 @@ node_support_input_i = [
   'access/amapi.h',
   'access/cmptype.h',
   'access/sdir.h',
+  'access/sequenceam.h',
   'access/tableam.h',
   'access/tsmapi.h',
   'commands/event_trigger.h',
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 0eba5ceb18a3..0d1e083822bb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3256,6 +3256,7 @@ typedef struct CreateSeqStmt
 	List	   *options;
 	Oid			ownerId;		/* ID of owner, or InvalidOid for default */
 	bool		for_identity;
+	char	   *accessMethod;	/* USING name of access method (eg. local) */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
 } CreateSeqStmt;
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index f723668da9ec..2815989ac0b6 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -55,6 +55,8 @@ extern bool check_log_connections(char **newval, void **extra, GucSource source)
 extern void assign_log_connections(const char *newval, void *extra);
 extern bool check_default_table_access_method(char **newval, void **extra,
 											  GucSource source);
+extern bool check_default_sequence_access_method(char **newval, void **extra,
+												 GucSource source);
 extern bool check_default_tablespace(char **newval, void **extra,
 									 GucSource source);
 extern bool check_default_text_search_config(char **newval, void **extra, GucSource source);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d03ab247788b..4851253ab066 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -188,6 +188,11 @@ typedef struct RelationData
 	 */
 	const struct TableAmRoutine *rd_tableam;
 
+	/*
+	 * Sequence access method.
+	 */
+	const struct SequenceAmRoutine *rd_sequenceam;
+
 	/* These are non-NULL only for an index relation: */
 	Form_pg_index rd_index;		/* pg_index tuple describing this index */
 	/* use "struct" here to avoid needing to include htup.h: */
diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile
index 2a3c8542cb33..b1ea8c6e87b4 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -14,6 +14,7 @@ include $(top_builddir)/src/Makefile.global
 
 OBJS = seqlocalam.o \
 	seqlocal_xlog.o \
-	sequence.o
+	sequence.o \
+	sequenceamapi.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build
index dea3b597a889..dfb4a391b090 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -4,4 +4,5 @@ backend_sources += files(
   'seqlocalam.c',
   'seqlocal_xlog.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
index 2b7bfb68d51d..2f0c3b5b4dda 100644
--- a/src/backend/access/sequence/seqlocalam.c
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -17,8 +17,8 @@
 
 #include "access/htup_details.h"
 #include "access/multixact.h"
-#include "access/seqlocalam.h"
 #include "access/seqlocal_xlog.h"
+#include "access/sequenceam.h"
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
@@ -27,6 +27,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /* Format of tuples stored in heap table associated to local sequences */
@@ -225,10 +226,10 @@ fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum)
  * Allocate a new value for a local sequence, based on the sequence
  * configuration.
  */
-int64
+static int64
 seq_local_nextval(Relation rel, int64 incby, int64 maxv,
-				  int64 minv, int64 cache, bool cycle,
-				  int64 *last)
+			  int64 minv, int64 cache, bool cycle,
+			  int64 *last)
 {
 	int64		result;
 	int64		fetch;
@@ -412,7 +413,7 @@ seq_local_nextval(Relation rel, int64 incby, int64 maxv,
  *
  * Return the table access method used by this sequence.
  */
-const char *
+static const char *
 seq_local_get_table_am(void)
 {
 	return DEFAULT_TABLE_ACCESS_METHOD;
@@ -427,7 +428,7 @@ seq_local_get_table_am(void)
  * inserted after the relation has been created, filling in its heap
  * table.
  */
-void
+static void
 seq_local_init(Relation rel, int64 last_value, bool is_called)
 {
 	Datum		value[SEQ_LOCAL_COL_LASTCOL];
@@ -494,7 +495,7 @@ seq_local_init(Relation rel, int64 last_value, bool is_called)
  *
  * Callback for setval().
  */
-void
+static void
 seq_local_setval(Relation rel, int64 next, bool iscalled)
 {
 	Buffer		buf;
@@ -542,7 +543,7 @@ seq_local_setval(Relation rel, int64 next, bool iscalled)
  * Perform a hard reset on the local sequence, rewriting its heap data
  * entirely.
  */
-void
+static void
 seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state)
 {
 	Form_pg_seq_local_data seq;
@@ -595,7 +596,7 @@ seq_local_reset(Relation rel, int64 startv, bool is_called, bool reset_state)
  *
  * Retrieve the state of a local sequence.
  */
-void
+static void
 seq_local_get_state(Relation rel,
 					int64 *last_value,
 					bool *is_called,
@@ -622,7 +623,7 @@ seq_local_get_state(Relation rel,
  *
  * Persistence change for the local sequence Relation.
  */
-void
+static void
 seq_local_change_persistence(Relation rel, char newrelpersistence)
 {
 	Buffer		buf;
@@ -633,3 +634,24 @@ seq_local_change_persistence(Relation rel, char newrelpersistence)
 	fill_seq_with_data(rel, &seqdatatuple);
 	UnlockReleaseBuffer(buf);
 }
+
+/* ------------------------------------------------------------------------
+ * Definition of the local sequence access method.
+ * ------------------------------------------------------------------------
+ */
+static const SequenceAmRoutine seq_local_methods = {
+	.type = T_SequenceAmRoutine,
+	.get_table_am = seq_local_get_table_am,
+	.init = seq_local_init,
+	.nextval = seq_local_nextval,
+	.setval = seq_local_setval,
+	.reset = seq_local_reset,
+	.get_state = seq_local_get_state,
+	.change_persistence = seq_local_change_persistence
+};
+
+Datum
+seq_local_sequenceam_handler(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_POINTER(&seq_local_methods);
+}
diff --git a/src/backend/access/sequence/sequence.c b/src/backend/access/sequence/sequence.c
index 106af1477e94..38180bb4f9bf 100644
--- a/src/backend/access/sequence/sequence.c
+++ b/src/backend/access/sequence/sequence.c
@@ -13,7 +13,8 @@
  *
  * NOTES
  *	  This file contains sequence_ routines that implement access to sequences
- *	  (in contrast to other relation types like indexes).
+ *	  (in contrast to other relation types like indexes) that are independent
+ *	  of individual sequence access methods.
  *
  *-------------------------------------------------------------------------
  */
diff --git a/src/backend/access/sequence/sequenceamapi.c b/src/backend/access/sequence/sequenceamapi.c
new file mode 100644
index 000000000000..cf70f9156afb
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,146 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/sequence/sequenceamapi.c
+ *
+ *
+ * Sequence access method allows the SQL Standard Sequence objects to be
+ * managed according to either the default access method or a pluggable
+ * replacement. Each sequence can only use one access method at a time,
+ * though different sequence access methods can be in use by different
+ * sequences at the same time.
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/sequenceam.h"
+#include "access/xact.h"
+#include "catalog/pg_am.h"
+#include "commands/defrem.h"
+#include "miscadmin.h"
+#include "utils/guc_hooks.h"
+#include "utils/syscache.h"
+
+
+/* GUC */
+char	   *default_sequence_access_method = DEFAULT_SEQUENCE_ACCESS_METHOD;
+
+/*
+ * GetSequenceAmRoutine
+ *		Call the specified access method handler routine to get its
+ *		SequenceAmRoutine struct, which will be palloc'd in the caller's
+ *		memory context.
+ */
+const SequenceAmRoutine *
+GetSequenceAmRoutine(Oid amhandler)
+{
+	Datum		datum;
+	SequenceAmRoutine *routine;
+
+	datum = OidFunctionCall0(amhandler);
+	routine = (SequenceAmRoutine *) DatumGetPointer(datum);
+
+	if (routine == NULL || !IsA(routine, SequenceAmRoutine))
+		elog(ERROR, "sequence access method handler %u did not return a SequenceAmRoutine struct",
+			 amhandler);
+
+	/*
+	 * Assert that all required callbacks are present.  That makes it a bit
+	 * easier to keep AMs up to date, e.g. when forward porting them to a new
+	 * major version.
+	 */
+	Assert(routine->get_table_am != NULL);
+	Assert(routine->init != NULL);
+	Assert(routine->nextval != NULL);
+	Assert(routine->setval != NULL);
+	Assert(routine->reset != NULL);
+	Assert(routine->get_state != NULL);
+	Assert(routine->change_persistence != NULL);
+
+	return routine;
+}
+
+/*
+ * GetSequenceAmRoutineId
+ *		Call pg_am and retrieve the OID of the access method handler.
+ */
+Oid
+GetSequenceAmRoutineId(Oid amoid)
+{
+	Oid			amhandleroid;
+	HeapTuple	tuple;
+	Form_pg_am	aform;
+
+	tuple = SearchSysCache1(AMOID,
+							ObjectIdGetDatum(amoid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for access method %u", amoid);
+	aform = (Form_pg_am) GETSTRUCT(tuple);
+	Assert(aform->amtype == AMTYPE_SEQUENCE);
+	amhandleroid = aform->amhandler;
+	ReleaseSysCache(tuple);
+
+	return amhandleroid;
+}
+
+/* check_hook: validate new default_sequence_access_method */
+bool
+check_default_sequence_access_method(char **newval, void **extra,
+									 GucSource source)
+{
+	if (**newval == '\0')
+	{
+		GUC_check_errdetail("%s cannot be empty.",
+							"default_sequence_access_method");
+		return false;
+	}
+
+	if (strlen(*newval) >= NAMEDATALEN)
+	{
+		GUC_check_errdetail("%s is too long (maximum %d characters).",
+							"default_sequence_access_method", NAMEDATALEN - 1);
+		return false;
+	}
+
+	/*
+	 * If we aren't inside a transaction, or not connected to a database, we
+	 * cannot do the catalog access necessary to verify the method.  Must
+	 * accept the value on faith.
+	 */
+	if (IsTransactionState() && MyDatabaseId != InvalidOid)
+	{
+		if (!OidIsValid(get_sequence_am_oid(*newval, true)))
+		{
+			/*
+			 * When source == PGC_S_TEST, don't throw a hard error for a
+			 * nonexistent sequence access method, only a NOTICE. See comments
+			 * in guc.h.
+			 */
+			if (source == PGC_S_TEST)
+			{
+				ereport(NOTICE,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("sequence access method \"%s\" does not exist",
+								*newval)));
+			}
+			else
+			{
+				GUC_check_errdetail("sequence access method \"%s\" does not exist.",
+									*newval);
+				return false;
+			}
+		}
+	}
+
+	return true;
+}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 606434823cf4..c7ebc003c5f4 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1490,9 +1490,13 @@ heap_create_with_catalog(const char *relname,
 		 * No need to add an explicit dependency for the toast table, as the
 		 * main table depends on it.  Partitioned tables may not have an
 		 * access method set.
+		 *
+		 * Sequences and tables are created with their access method ID
+		 * given by the caller of this function.
 		 */
 		if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE) ||
-			(relkind == RELKIND_PARTITIONED_TABLE && OidIsValid(accessmtd)))
+			(relkind == RELKIND_PARTITIONED_TABLE && OidIsValid(accessmtd)) ||
+			RELKIND_HAS_SEQUENCE_AM(relkind))
 		{
 			ObjectAddressSet(referenced, AccessMethodRelationId, accessmtd);
 			add_exact_object_address(&referenced, addrs);
diff --git a/src/backend/commands/amcmds.c b/src/backend/commands/amcmds.c
index 21825e8c3f54..14004388a32d 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -14,6 +14,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
@@ -175,6 +176,16 @@ get_table_am_oid(const char *amname, bool missing_ok)
 	return get_am_type_oid(amname, AMTYPE_TABLE, missing_ok);
 }
 
+/*
+ * get_sequence_am_oid - given an access method name, look up its OID
+ *		and verify it corresponds to an sequence AM.
+ */
+Oid
+get_sequence_am_oid(const char *amname, bool missing_ok)
+{
+	return get_am_type_oid(amname, AMTYPE_SEQUENCE, missing_ok);
+}
+
 /*
  * get_am_oid - given an access method name, look up its OID.
  *		The type is not checked.
@@ -215,6 +226,8 @@ get_am_type_string(char amtype)
 	{
 		case AMTYPE_INDEX:
 			return "INDEX";
+		case AMTYPE_SEQUENCE:
+			return "SEQUENCE";
 		case AMTYPE_TABLE:
 			return "TABLE";
 		default:
@@ -251,6 +264,9 @@ lookup_am_handler_func(List *handler_name, char amtype)
 		case AMTYPE_INDEX:
 			expectedType = INDEX_AM_HANDLEROID;
 			break;
+		case AMTYPE_SEQUENCE:
+			expectedType = SEQUENCE_AM_HANDLEROID;
+			break;
 		case AMTYPE_TABLE:
 			expectedType = TABLE_AM_HANDLEROID;
 			break;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index ea90db9ddd7b..9ae238dd494f 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -17,8 +17,8 @@
 #include "access/htup_details.h"
 #include "access/multixact.h"
 #include "access/relation.h"
-#include "access/seqlocalam.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -150,13 +150,16 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->inhRelations = NIL;
 	stmt->constraints = NIL;
 	stmt->options = NIL;
+	stmt->accessMethod = seq->accessMethod ? pstrdup(seq->accessMethod) : NULL;
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
 
 	/*
 	 * Initial relation has no attributes, these are added later after the
-	 * relation has been created in the catalogs.
+	 * relation has been created in the catalogs.  A sequence access method
+	 * can create its own set of attributes in its init() callback once
+	 * the relation has been created in the catalogs.
 	 */
 	stmt->tableElts = NIL;
 
@@ -167,7 +170,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	rel = sequence_open(seqoid, AccessExclusiveLock);
 
 	/* now initialize the sequence table structure and its data */
-	seq_local_init(rel, last_value, is_called);
+	sequence_init(rel, last_value, is_called);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -235,7 +238,7 @@ ResetSequence(Oid seq_relid)
 	ReleaseSysCache(pgstuple);
 
 	/* Sequence state is forcibly reset here. */
-	seq_local_reset(seq_rel, startv, false, true);
+	sequence_reset(seq_rel, startv, false, true);
 
 	/* Clear local cache so that we don't think we have cached numbers */
 	/* Note that we do not change the currval() state */
@@ -292,7 +295,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
 
 	/* Read sequence data */
-	seq_local_get_state(seqrel, &last_value, &is_called, &page_lsn);
+	sequence_get_state(seqrel, &last_value, &is_called, &page_lsn);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -305,7 +308,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		if (RelationNeedsWAL(seqrel))
 			GetTopTransactionId();
 
-		seq_local_reset(seqrel, last_value, is_called, reset_state);
+		sequence_reset(seqrel, last_value, is_called, reset_state);
 	}
 
 	/* Clear local cache so that we don't think we have cached numbers */
@@ -348,7 +351,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence)
 	if (RelationNeedsWAL(seqrel))
 		GetTopTransactionId();
 
-	seq_local_change_persistence(seqrel, newrelpersistence);
+	sequence_change_persistence(seqrel, newrelpersistence);
 
 	sequence_close(seqrel, NoLock);
 }
@@ -465,8 +468,8 @@ nextval_internal(Oid relid, bool check_permissions)
 	ReleaseSysCache(pgstuple);
 
 	/* retrieve next value from the access method */
-	result = seq_local_nextval(seqrel, incby, maxv, minv, cache, cycle,
-							   &last);
+	result = sequence_nextval(seqrel, incby, maxv, minv, cache, cycle,
+							  &last);
 
 	/* save info in local cache */
 	elm->increment = incby;
@@ -620,7 +623,7 @@ SetSequence(Oid relid, int64 next, bool iscalled)
 		GetTopTransactionId();
 
 	/* Call the access method callback */
-	seq_local_setval(seqrel, next, iscalled);
+	sequence_setval(seqrel, next, iscalled);
 
 	sequence_close(seqrel, NoLock);
 }
@@ -1352,8 +1355,7 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		int64		last_value;
 		XLogRecPtr	page_lsn;
 
-		seq_local_get_state(seqrel, &last_value, &is_called,
-							&page_lsn);
+		sequence_get_state(seqrel, &last_value, &is_called, &page_lsn);
 
 		values[0] = Int64GetDatum(last_value);
 		values[1] = BoolGetDatum(is_called);
@@ -1402,7 +1404,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		seq_local_get_state(seqrel, &result, &is_called, &page_lsn);
+		sequence_get_state(seqrel, &result, &is_called, &page_lsn);
 	}
 
 	sequence_close(seqrel, NoLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 9cb90c9bffa2..054e71889657 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23,6 +23,7 @@
 #include "access/reloptions.h"
 #include "access/relscan.h"
 #include "access/sysattr.h"
+#include "access/sequenceam.h"
 #include "access/tableam.h"
 #include "access/toast_compression.h"
 #include "access/xact.h"
@@ -1030,14 +1031,18 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	}
 
 	/*
-	 * For relations with table AM and partitioned tables, select access
-	 * method to use: an explicitly indicated one, or (in the case of a
+	 * For relations with table AM, partitioned tables or sequences, select
+	 * access method to use: an explicitly indicated one, or (in the case of a
 	 * partitioned table) the parent's, if it has one.
 	 */
 	if (stmt->accessMethod != NULL)
 	{
-		Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE);
-		accessMethodId = get_table_am_oid(stmt->accessMethod, false);
+		Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE ||
+			   RELKIND_HAS_SEQUENCE_AM(relkind));
+		if (RELKIND_HAS_SEQUENCE_AM(relkind))
+			accessMethodId = get_sequence_am_oid(stmt->accessMethod, false);
+		else
+			accessMethodId = get_table_am_oid(stmt->accessMethod, false);
 	}
 	else if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE)
 	{
@@ -1050,6 +1055,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		if (RELKIND_HAS_TABLE_AM(relkind) && !OidIsValid(accessMethodId))
 			accessMethodId = get_table_am_oid(default_table_access_method, false);
 	}
+	else if (RELKIND_HAS_SEQUENCE_AM(relkind))
+	{
+		accessMethodId = get_sequence_am_oid(default_sequence_access_method, false);
+	}
 
 	/*
 	 * Create the relation.  Inherited defaults and CHECK constraints are
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 77ddb9ca53f1..64d4dccc936f 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -48,6 +48,7 @@ node_headers = \
 	access/amapi.h \
 	access/cmptype.h \
 	access/sdir.h \
+	access/sequenceam.h \
 	access/tableam.h \
 	access/tsmapi.h \
 	commands/event_trigger.h \
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 4308751f787e..fd8f1350083e 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -60,6 +60,7 @@ my @all_input_files = qw(
   access/amapi.h
   access/cmptype.h
   access/sdir.h
+  access/sequenceam.h
   access/tableam.h
   access/tsmapi.h
   commands/event_trigger.h
@@ -84,6 +85,7 @@ my @nodetag_only_files = qw(
   nodes/execnodes.h
   access/amapi.h
   access/sdir.h
+  access/sequenceam.h
   access/tableam.h
   access/tsmapi.h
   commands/event_trigger.h
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 713ee5c10a21..0d0544d3f06e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -389,6 +389,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		copy_file_name
 				access_method_clause attr_name
 				table_access_method_clause name cursor_name file_name
+				sequence_access_method_clause
 				cluster_index_specification
 
 %type <list>	func_name handler_name qual_Op qual_all_Op subquery_Op
@@ -5062,23 +5063,26 @@ RefreshMatViewStmt:
 
 CreateSeqStmt:
 			CREATE OptTemp SEQUENCE qualified_name OptSeqOptList
+				sequence_access_method_clause
 				{
 					CreateSeqStmt *n = makeNode(CreateSeqStmt);
-
 					$4->relpersistence = $2;
 					n->sequence = $4;
 					n->options = $5;
+					n->accessMethod = $6;
 					n->ownerId = InvalidOid;
 					n->if_not_exists = false;
 					$$ = (Node *) n;
 				}
 			| CREATE OptTemp SEQUENCE IF_P NOT EXISTS qualified_name OptSeqOptList
+				sequence_access_method_clause
 				{
 					CreateSeqStmt *n = makeNode(CreateSeqStmt);
 
 					$7->relpersistence = $2;
 					n->sequence = $7;
 					n->options = $8;
+					n->accessMethod = $9;
 					n->ownerId = InvalidOid;
 					n->if_not_exists = true;
 					$$ = (Node *) n;
@@ -5115,6 +5119,11 @@ OptParenthesizedSeqOptList: '(' SeqOptList ')'		{ $$ = $2; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+sequence_access_method_clause:
+			USING name							{ $$ = $2; }
+			| /*EMPTY*/							{ $$ = NULL; }
+		;
+
 SeqOptList: SeqOptElem								{ $$ = list_make1($1); }
 			| SeqOptList SeqOptElem					{ $$ = lappend($1, $2); }
 		;
@@ -6118,6 +6127,7 @@ CreateAmStmt: CREATE ACCESS METHOD name TYPE_P am_type HANDLER handler_name
 
 am_type:
 			INDEX			{ $$ = AMTYPE_INDEX; }
+		|	SEQUENCE		{ $$ = AMTYPE_SEQUENCE; }
 		|	TABLE			{ $$ = AMTYPE_TABLE; }
 		;
 
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 3f4393b52eae..19cae9e51807 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -26,6 +26,7 @@
 #include "access/htup_details.h"
 #include "access/relation.h"
 #include "access/reloptions.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/toast_compression.h"
 #include "catalog/dependency.h"
@@ -521,6 +522,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->sequence->relpersistence = seqpersistence;
 	seqstmt->options = seqoptions;
+	seqstmt->accessMethod = NULL;
 
 	/*
 	 * If a sequence data type was specified, add it to the options.  Prepend
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 4581c4b16977..ae99185d8213 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -369,6 +369,7 @@ PSEUDOTYPE_DUMMY_IO_FUNCS(language_handler);
 PSEUDOTYPE_DUMMY_IO_FUNCS(fdw_handler);
 PSEUDOTYPE_DUMMY_IO_FUNCS(table_am_handler);
 PSEUDOTYPE_DUMMY_IO_FUNCS(index_am_handler);
+PSEUDOTYPE_DUMMY_IO_FUNCS(sequence_am_handler);
 PSEUDOTYPE_DUMMY_IO_FUNCS(tsm_handler);
 PSEUDOTYPE_DUMMY_IO_FUNCS(internal);
 PSEUDOTYPE_DUMMY_IO_FUNCS(anyelement);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 6b634c9fff10..cf78bb779771 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -34,6 +34,7 @@
 #include "access/multixact.h"
 #include "access/parallel.h"
 #include "access/reloptions.h"
+#include "access/sequenceam.h"
 #include "access/sysattr.h"
 #include "access/table.h"
 #include "access/tableam.h"
@@ -64,6 +65,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "commands/defrem.h"
 #include "commands/policy.h"
 #include "commands/publicationcmds.h"
 #include "commands/trigger.h"
@@ -302,6 +304,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
 static void RelationBuildTupleDesc(Relation relation);
 static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
 static void RelationInitPhysicalAddr(Relation relation);
+static void RelationInitSequenceAccessMethod(Relation relation);
 static void load_critical_index(Oid indexoid, Oid heapoid);
 static TupleDesc GetPgClassDescriptor(void);
 static TupleDesc GetPgIndexDescriptor(void);
@@ -1225,8 +1228,7 @@ retry:
 	if (relation->rd_rel->relkind == RELKIND_INDEX ||
 		relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
 		RelationInitIndexAccessInfo(relation);
-	else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) ||
-			 relation->rd_rel->relkind == RELKIND_SEQUENCE)
+	else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))
 		RelationInitTableAccessMethod(relation);
 	else if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
@@ -1235,6 +1237,8 @@ retry:
 		 * inherit.
 		 */
 	}
+	else if (RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind))
+		RelationInitSequenceAccessMethod(relation);
 	else
 		Assert(relation->rd_rel->relam == InvalidOid);
 
@@ -1826,17 +1830,9 @@ RelationInitTableAccessMethod(Relation relation)
 	HeapTuple	tuple;
 	Form_pg_am	aform;
 
-	if (relation->rd_rel->relkind == RELKIND_SEQUENCE)
-	{
-		/*
-		 * Sequences are currently accessed like heap tables, but it doesn't
-		 * seem prudent to show that in the catalog. So just overwrite it
-		 * here.
-		 */
-		Assert(relation->rd_rel->relam == InvalidOid);
-		relation->rd_amhandler = F_HEAP_TABLEAM_HANDLER;
-	}
-	else if (IsCatalogRelation(relation))
+	Assert(RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind));
+
+	if (IsCatalogRelation(relation))
 	{
 		/*
 		 * Avoid doing a syscache lookup for catalog tables.
@@ -1867,6 +1863,49 @@ RelationInitTableAccessMethod(Relation relation)
 	InitTableAmRoutine(relation);
 }
 
+/*
+ * Initialize sequence-access-method support data for a sequence relation
+ */
+static void
+RelationInitSequenceAccessMethod(Relation relation)
+{
+	HeapTuple	tuple;
+	Form_pg_am	aform;
+	const char *tableam_name;
+	Oid			tableam_oid;
+	Oid			tableam_handler;
+
+	Assert(RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind));
+
+	/*
+	 * Look up the sequence access method, save the OID of its handler
+	 * function.
+	 */
+	Assert(relation->rd_rel->relam != InvalidOid);
+	relation->rd_amhandler = GetSequenceAmRoutineId(relation->rd_rel->relam);
+
+	/*
+	 * Now we can fetch the sequence AM's API struct.
+	 */
+	relation->rd_sequenceam = GetSequenceAmRoutine(relation->rd_amhandler);
+
+	/*
+	 * From the sequence AM, set its expected table access method.
+	 */
+	tableam_name = sequence_get_table_am(relation);
+	tableam_oid = get_table_am_oid(tableam_name, false);
+
+	tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(tableam_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for access method %u",
+			 tableam_oid);
+	aform = (Form_pg_am) GETSTRUCT(tuple);
+	tableam_handler = aform->amhandler;
+	ReleaseSysCache(tuple);
+
+	relation->rd_tableam = GetTableAmRoutine(tableam_handler);
+}
+
 /*
  *		formrdesc
  *
@@ -3706,14 +3745,17 @@ RelationBuildLocalRelation(const char *relname,
 	rel->rd_rel->relam = accessmtd;
 
 	/*
-	 * RelationInitTableAccessMethod will do syscache lookups, so we mustn't
-	 * run it in CacheMemoryContext.  Fortunately, the remaining steps don't
-	 * require a long-lived current context.
+	 * RelationInitTableAccessMethod() and RelationInitSequenceAccessMethod()
+	 * will do syscache lookups, so we mustn't run them in CacheMemoryContext.
+	 * Fortunately, the remaining steps don't require a long-lived current
+	 * context.
 	 */
 	MemoryContextSwitchTo(oldcxt);
 
-	if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_SEQUENCE)
+	if (RELKIND_HAS_TABLE_AM(relkind))
 		RelationInitTableAccessMethod(rel);
+	else if (relkind == RELKIND_SEQUENCE)
+		RelationInitSequenceAccessMethod(rel);
 
 	/*
 	 * Leave index access method uninitialized, because the pg_index row has
@@ -4338,13 +4380,21 @@ RelationCacheInitializePhase3(void)
 
 		/* Reload tableam data if needed */
 		if (relation->rd_tableam == NULL &&
-			(RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || relation->rd_rel->relkind == RELKIND_SEQUENCE))
+			(RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind)))
 		{
 			RelationInitTableAccessMethod(relation);
 			Assert(relation->rd_tableam != NULL);
 
 			restart = true;
 		}
+		else if (relation->rd_sequenceam == NULL &&
+				 relation->rd_rel->relkind == RELKIND_SEQUENCE)
+		{
+			RelationInitSequenceAccessMethod(relation);
+			Assert(relation->rd_sequenceam != NULL);
+
+			restart = true;
+		}
 
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
@@ -6420,8 +6470,10 @@ load_relcache_init_file(bool shared)
 				nailed_rels++;
 
 			/* Load table AM data */
-			if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind) || rel->rd_rel->relkind == RELKIND_SEQUENCE)
+			if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind))
 				RelationInitTableAccessMethod(rel);
+			else if (rel->rd_rel->relkind == RELKIND_SEQUENCE)
+				RelationInitSequenceAccessMethod(rel);
 
 			Assert(rel->rd_index == NULL);
 			Assert(rel->rd_indextuple == NULL);
@@ -6433,6 +6485,7 @@ load_relcache_init_file(bool shared)
 			Assert(rel->rd_supportinfo == NULL);
 			Assert(rel->rd_indoption == NULL);
 			Assert(rel->rd_indcollation == NULL);
+			Assert(rel->rd_sequenceam == NULL);
 			Assert(rel->rd_opcoptions == NULL);
 		}
 
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 7c60b1255646..7cf0d89a8c6d 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -698,6 +698,14 @@
   ifdef => 'DEBUG_NODE_TESTS_ENABLED',
 },
 
+{ name => 'default_sequence_access_method', type => 'string', context => 'PGC_USERSET', group => 'CLIENT_CONN_STATEMENT',
+  short_desc => 'Sets the default sequence access method for new sequences.',
+  flags => 'GUC_IS_NAME',
+  variable => 'default_sequence_access_method',
+  boot_val => 'DEFAULT_SEQUENCE_ACCESS_METHOD',
+  check_hook => 'check_default_sequence_access_method',
+},
+
 { name => 'default_statistics_target', type => 'int', context => 'PGC_USERSET', group => 'QUERY_TUNING_OTHER',
   short_desc => 'Sets the default statistics target.',
   long_desc => 'This applies to table columns that have not had a column-specific target set via ALTER TABLE SET STATISTICS.',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 73ff6ad0a32e..889114f7d8ab 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -33,6 +33,7 @@
 #include "access/gin.h"
 #include "access/slru.h"
 #include "access/toast_compression.h"
+#include "access/sequenceam.h"
 #include "access/twophase.h"
 #include "access/xlog_internal.h"
 #include "access/xlogprefetcher.h"
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a7..4c92e058284e 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -758,6 +758,7 @@
                                         #   error
 #search_path = '"$user", public'        # schema names
 #row_security = on
+#default_sequence_access_method = 'seqlocal'
 #default_table_access_method = 'heap'
 #default_tablespace = ''                # a tablespace name, '' uses the default
 #default_toast_compression = 'pglz'     # 'pglz' or 'lz4'
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 3584c4e1428c..a66b15362e86 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -168,10 +168,12 @@ describeAccessMethods(const char *pattern, bool verbose)
 					  "SELECT amname AS \"%s\",\n"
 					  "  CASE amtype"
 					  " WHEN " CppAsString2(AMTYPE_INDEX) " THEN '%s'"
+					  " WHEN " CppAsString2(AMTYPE_SEQUENCE) " THEN '%s'"
 					  " WHEN " CppAsString2(AMTYPE_TABLE) " THEN '%s'"
 					  " END AS \"%s\"",
 					  gettext_noop("Name"),
 					  gettext_noop("Index"),
+					  gettext_noop("Sequence"),
 					  gettext_noop("Table"),
 					  gettext_noop("Type"));
 
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 06edea98f060..bdb600b4671a 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2620,7 +2620,7 @@ match_previous_words(int pattern_id,
 	else if (Matches("ALTER", "SEQUENCE", MatchAny))
 		COMPLETE_WITH("AS", "INCREMENT", "MINVALUE", "MAXVALUE", "RESTART",
 					  "START", "NO", "CACHE", "CYCLE", "SET", "OWNED BY",
-					  "OWNER TO", "RENAME TO");
+					  "OWNER TO", "RENAME TO", "USING");
 	/* ALTER SEQUENCE <name> AS */
 	else if (TailMatches("ALTER", "SEQUENCE", MatchAny, "AS"))
 		COMPLETE_WITH_CS("smallint", "integer", "bigint");
@@ -3437,7 +3437,7 @@ match_previous_words(int pattern_id,
 		COMPLETE_WITH("TYPE");
 	/* Complete "CREATE ACCESS METHOD <name> TYPE" */
 	else if (Matches("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE"))
-		COMPLETE_WITH("INDEX", "TABLE");
+		COMPLETE_WITH("INDEX", "SEQUENCE", "TABLE");
 	/* Complete "CREATE ACCESS METHOD <name> TYPE <type>" */
 	else if (Matches("CREATE", "ACCESS", "METHOD", MatchAny, "TYPE", MatchAny))
 		COMPLETE_WITH("HANDLER");
@@ -3735,7 +3735,7 @@ match_previous_words(int pattern_id,
 	else if (TailMatches("CREATE", "SEQUENCE", MatchAny) ||
 			 TailMatches("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
 		COMPLETE_WITH("AS", "INCREMENT BY", "MINVALUE", "MAXVALUE", "NO",
-					  "CACHE", "CYCLE", "OWNED BY", "START WITH");
+					  "CACHE", "CYCLE", "OWNED BY", "START WITH", "USING");
 	else if (TailMatches("CREATE", "SEQUENCE", MatchAny, "AS") ||
 			 TailMatches("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "AS"))
 		COMPLETE_WITH_CS("smallint", "integer", "bigint");
diff --git a/src/test/regress/expected/create_am.out b/src/test/regress/expected/create_am.out
index c1a951572512..784870e603d1 100644
--- a/src/test/regress/expected/create_am.out
+++ b/src/test/regress/expected/create_am.out
@@ -163,11 +163,6 @@ CREATE VIEW tableam_view_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2;
 ERROR:  syntax error at or near "USING"
 LINE 1: CREATE VIEW tableam_view_heap2 USING heap2 AS SELECT * FROM ...
                                        ^
--- CREATE SEQUENCE doesn't support USING
-CREATE SEQUENCE tableam_seq_heap2 USING heap2;
-ERROR:  syntax error at or near "USING"
-LINE 1: CREATE SEQUENCE tableam_seq_heap2 USING heap2;
-                                          ^
 -- CREATE MATERIALIZED VIEW does support USING
 CREATE MATERIALIZED VIEW tableam_tblmv_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2;
 SELECT f1 FROM tableam_tblmv_heap2 ORDER BY f1;
@@ -514,9 +509,12 @@ CREATE TABLE tableam_parted_heapx (a text, b int) PARTITION BY list (a);
 CREATE TABLE tableam_parted_1_heapx PARTITION OF tableam_parted_heapx FOR VALUES IN ('a', 'b');
 -- but an explicitly set AM overrides it
 CREATE TABLE tableam_parted_2_heapx PARTITION OF tableam_parted_heapx FOR VALUES IN ('c', 'd') USING heap;
--- sequences, views and foreign servers shouldn't have an AM
-CREATE VIEW tableam_view_heapx AS SELECT * FROM tableam_tbl_heapx;
+-- sequences have an AM
+SET LOCAL default_sequence_access_method = 'seqlocal';
 CREATE SEQUENCE tableam_seq_heapx;
+RESET default_sequence_access_method;
+-- views and foreign servers shouldn't have an AM
+CREATE VIEW tableam_view_heapx AS SELECT * FROM tableam_tbl_heapx;
 CREATE FOREIGN DATA WRAPPER fdw_heap2 VALIDATOR postgresql_fdw_validator;
 CREATE SERVER fs_heap2 FOREIGN DATA WRAPPER fdw_heap2 ;
 CREATE FOREIGN table tableam_fdw_heapx () SERVER fs_heap2;
@@ -533,18 +531,18 @@ FROM pg_class AS pc
     LEFT JOIN pg_am AS pa ON (pa.oid = pc.relam)
 WHERE pc.relname LIKE 'tableam_%_heapx'
 ORDER BY 3, 1, 2;
- relkind | amname |           relname           
----------+--------+-----------------------------
- f       |        | tableam_fdw_heapx
- r       | heap2  | tableam_parted_1_heapx
- r       | heap   | tableam_parted_2_heapx
- p       |        | tableam_parted_heapx
- S       |        | tableam_seq_heapx
- r       | heap2  | tableam_tbl_heapx
- r       | heap2  | tableam_tblas_heapx
- m       | heap2  | tableam_tblmv_heapx
- r       | heap2  | tableam_tblselectinto_heapx
- v       |        | tableam_view_heapx
+ relkind |  amname  |           relname           
+---------+----------+-----------------------------
+ f       |          | tableam_fdw_heapx
+ r       | heap2    | tableam_parted_1_heapx
+ r       | heap     | tableam_parted_2_heapx
+ p       |          | tableam_parted_heapx
+ S       | seqlocal | tableam_seq_heapx
+ r       | heap2    | tableam_tbl_heapx
+ r       | heap2    | tableam_tblas_heapx
+ m       | heap2    | tableam_tblmv_heapx
+ r       | heap2    | tableam_tblselectinto_heapx
+ v       |          | tableam_view_heapx
 (10 rows)
 
 -- don't want to keep those tables, nor the default
@@ -574,3 +572,22 @@ table tableam_parted_b_heap2 depends on access method heap2
 table tableam_parted_d_heap2 depends on access method heap2
 HINT:  Use DROP ... CASCADE to drop the dependent objects too.
 -- we intentionally leave the objects created above alive, to verify pg_dump support
+-- Checks for sequence access methods
+-- Create new sequence access method which uses standard local handler
+CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER seq_local_sequenceam_handler;
+-- Create and use sequence
+CREATE SEQUENCE test_seqam USING local2;
+SELECT nextval('test_seqam'::regclass);
+ nextval 
+---------
+       1
+(1 row)
+
+-- Try to drop and fail on dependency
+DROP ACCESS METHOD local2;
+ERROR:  cannot drop access method local2 because other objects depend on it
+DETAIL:  sequence test_seqam depends on access method local2
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- And cleanup
+DROP SEQUENCE test_seqam;
+DROP ACCESS METHOD local2;
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 6ff4d7ee9014..7f4434066ec3 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1950,6 +1950,18 @@ WHERE p1.oid = a1.amhandler AND a1.amtype = 't' AND
 -----+--------+-----+---------
 (0 rows)
 
+-- check for sequence amhandler functions with the wrong signature
+SELECT a1.oid, a1.amname, p1.oid, p1.proname
+FROM pg_am AS a1, pg_proc AS p1
+WHERE p1.oid = a1.amhandler AND a1.amtype = 's' AND
+    (p1.prorettype != 'sequence_am_handler'::regtype
+     OR p1.proretset
+     OR p1.pronargs != 1
+     OR p1.proargtypes[0] != 'internal'::regtype);
+ oid | amname | oid | proname 
+-----+--------+-----+---------
+(0 rows)
+
 -- **************** pg_amop ****************
 -- Look for illegal values in pg_amop fields
 SELECT a1.amopfamily, a1.amopstrategy
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index c8f3932edf09..3203b5376989 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5161,31 +5161,33 @@ Indexes:
 -- check printing info about access methods
 \dA
 List of access methods
-  Name  | Type  
---------+-------
- brin   | Index
- btree  | Index
- gin    | Index
- gist   | Index
- hash   | Index
- heap   | Table
- heap2  | Table
- spgist | Index
-(8 rows)
+   Name   |   Type   
+----------+----------
+ brin     | Index
+ btree    | Index
+ gin      | Index
+ gist     | Index
+ hash     | Index
+ heap     | Table
+ heap2    | Table
+ seqlocal | Sequence
+ spgist   | Index
+(9 rows)
 
 \dA *
 List of access methods
-  Name  | Type  
---------+-------
- brin   | Index
- btree  | Index
- gin    | Index
- gist   | Index
- hash   | Index
- heap   | Table
- heap2  | Table
- spgist | Index
-(8 rows)
+   Name   |   Type   
+----------+----------
+ brin     | Index
+ btree    | Index
+ gin      | Index
+ gist     | Index
+ hash     | Index
+ heap     | Table
+ heap2    | Table
+ seqlocal | Sequence
+ spgist   | Index
+(9 rows)
 
 \dA h*
 List of access methods
@@ -5210,32 +5212,34 @@ List of access methods
 
 \dA: extra argument "bar" ignored
 \dA+
-                             List of access methods
-  Name  | Type  |       Handler        |              Description               
---------+-------+----------------------+----------------------------------------
- brin   | Index | brinhandler          | block range index (BRIN) access method
- btree  | Index | bthandler            | b-tree index access method
- gin    | Index | ginhandler           | GIN index access method
- gist   | Index | gisthandler          | GiST index access method
- hash   | Index | hashhandler          | hash index access method
- heap   | Table | heap_tableam_handler | heap table access method
- heap2  | Table | heap_tableam_handler | 
- spgist | Index | spghandler           | SP-GiST index access method
-(8 rows)
+                                   List of access methods
+   Name   |   Type   |           Handler            |              Description               
+----------+----------+------------------------------+----------------------------------------
+ brin     | Index    | brinhandler                  | block range index (BRIN) access method
+ btree    | Index    | bthandler                    | b-tree index access method
+ gin      | Index    | ginhandler                   | GIN index access method
+ gist     | Index    | gisthandler                  | GiST index access method
+ hash     | Index    | hashhandler                  | hash index access method
+ heap     | Table    | heap_tableam_handler         | heap table access method
+ heap2    | Table    | heap_tableam_handler         | 
+ seqlocal | Sequence | seq_local_sequenceam_handler | local sequence access method
+ spgist   | Index    | spghandler                   | SP-GiST index access method
+(9 rows)
 
 \dA+ *
-                             List of access methods
-  Name  | Type  |       Handler        |              Description               
---------+-------+----------------------+----------------------------------------
- brin   | Index | brinhandler          | block range index (BRIN) access method
- btree  | Index | bthandler            | b-tree index access method
- gin    | Index | ginhandler           | GIN index access method
- gist   | Index | gisthandler          | GiST index access method
- hash   | Index | hashhandler          | hash index access method
- heap   | Table | heap_tableam_handler | heap table access method
- heap2  | Table | heap_tableam_handler | 
- spgist | Index | spghandler           | SP-GiST index access method
-(8 rows)
+                                   List of access methods
+   Name   |   Type   |           Handler            |              Description               
+----------+----------+------------------------------+----------------------------------------
+ brin     | Index    | brinhandler                  | block range index (BRIN) access method
+ btree    | Index    | bthandler                    | b-tree index access method
+ gin      | Index    | ginhandler                   | GIN index access method
+ gist     | Index    | gisthandler                  | GiST index access method
+ hash     | Index    | hashhandler                  | hash index access method
+ heap     | Table    | heap_tableam_handler         | heap table access method
+ heap2    | Table    | heap_tableam_handler         | 
+ seqlocal | Sequence | seq_local_sequenceam_handler | local sequence access method
+ spgist   | Index    | spghandler                   | SP-GiST index access method
+(9 rows)
 
 \dA+ h*
                      List of access methods
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index 9ddcacec6bf4..67c3c63503db 100644
--- a/src/test/regress/expected/type_sanity.out
+++ b/src/test/regress/expected/type_sanity.out
@@ -511,21 +511,21 @@ WHERE relkind NOT IN ('r', 'i', 'S', 't', 'v', 'm', 'c', 'f', 'p', 'I') OR
 -----+---------
 (0 rows)
 
--- All tables, indexes, partitioned indexes and matviews should have an
--- access method.
+-- All tables, indexes, partitioned indexes, matviews and sequences should have
+-- an access method.
 SELECT c1.oid, c1.relname
 FROM pg_class as c1
-WHERE c1.relkind NOT IN ('S', 'v', 'f', 'c', 'p') and
+WHERE c1.relkind NOT IN ('v', 'f', 'c', 'p') and
     c1.relam = 0;
  oid | relname 
 -----+---------
 (0 rows)
 
--- Conversely, sequences, views, foreign tables, types and partitioned
--- tables shouldn't have them.
+-- Conversely, views, foreign tables, types and partitioned tables
+-- shouldn't have them.
 SELECT c1.oid, c1.relname
 FROM pg_class as c1
-WHERE c1.relkind IN ('S', 'v', 'f', 'c', 'p') and
+WHERE c1.relkind IN ('v', 'f', 'c', 'p') and
     c1.relam != 0;
  oid | relname 
 -----+---------
diff --git a/src/test/regress/sql/create_am.sql b/src/test/regress/sql/create_am.sql
index 754fe0c694bc..76a91cf8dd68 100644
--- a/src/test/regress/sql/create_am.sql
+++ b/src/test/regress/sql/create_am.sql
@@ -117,9 +117,6 @@ SELECT INTO tableam_tblselectinto_heap2 USING heap2 FROM tableam_tbl_heap2;
 -- CREATE VIEW doesn't support USING
 CREATE VIEW tableam_view_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2;
 
--- CREATE SEQUENCE doesn't support USING
-CREATE SEQUENCE tableam_seq_heap2 USING heap2;
-
 -- CREATE MATERIALIZED VIEW does support USING
 CREATE MATERIALIZED VIEW tableam_tblmv_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2;
 SELECT f1 FROM tableam_tblmv_heap2 ORDER BY f1;
@@ -327,9 +324,13 @@ CREATE TABLE tableam_parted_1_heapx PARTITION OF tableam_parted_heapx FOR VALUES
 -- but an explicitly set AM overrides it
 CREATE TABLE tableam_parted_2_heapx PARTITION OF tableam_parted_heapx FOR VALUES IN ('c', 'd') USING heap;
 
--- sequences, views and foreign servers shouldn't have an AM
-CREATE VIEW tableam_view_heapx AS SELECT * FROM tableam_tbl_heapx;
+-- sequences have an AM
+SET LOCAL default_sequence_access_method = 'seqlocal';
 CREATE SEQUENCE tableam_seq_heapx;
+RESET default_sequence_access_method;
+
+-- views and foreign servers shouldn't have an AM
+CREATE VIEW tableam_view_heapx AS SELECT * FROM tableam_tbl_heapx;
 CREATE FOREIGN DATA WRAPPER fdw_heap2 VALIDATOR postgresql_fdw_validator;
 CREATE SERVER fs_heap2 FOREIGN DATA WRAPPER fdw_heap2 ;
 CREATE FOREIGN table tableam_fdw_heapx () SERVER fs_heap2;
@@ -365,3 +366,16 @@ CREATE FOREIGN TABLE fp PARTITION OF tableam_parted_a_heap2 DEFAULT SERVER x;
 DROP ACCESS METHOD heap2;
 
 -- we intentionally leave the objects created above alive, to verify pg_dump support
+
+-- Checks for sequence access methods
+
+-- Create new sequence access method which uses standard local handler
+CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER seq_local_sequenceam_handler;
+-- Create and use sequence
+CREATE SEQUENCE test_seqam USING local2;
+SELECT nextval('test_seqam'::regclass);
+-- Try to drop and fail on dependency
+DROP ACCESS METHOD local2;
+-- And cleanup
+DROP SEQUENCE test_seqam;
+DROP ACCESS METHOD local2;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index cd674d7dbca3..54a6e65a76ce 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1236,6 +1236,16 @@ WHERE p1.oid = a1.amhandler AND a1.amtype = 't' AND
      OR p1.pronargs != 1
      OR p1.proargtypes[0] != 'internal'::regtype);
 
+-- check for sequence amhandler functions with the wrong signature
+
+SELECT a1.oid, a1.amname, p1.oid, p1.proname
+FROM pg_am AS a1, pg_proc AS p1
+WHERE p1.oid = a1.amhandler AND a1.amtype = 's' AND
+    (p1.prorettype != 'sequence_am_handler'::regtype
+     OR p1.proretset
+     OR p1.pronargs != 1
+     OR p1.proargtypes[0] != 'internal'::regtype);
+
 -- **************** pg_amop ****************
 
 -- Look for illegal values in pg_amop fields
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index c2496823d90e..aa8da134a56f 100644
--- a/src/test/regress/sql/type_sanity.sql
+++ b/src/test/regress/sql/type_sanity.sql
@@ -370,18 +370,18 @@ WHERE relkind NOT IN ('r', 'i', 'S', 't', 'v', 'm', 'c', 'f', 'p', 'I') OR
     relpersistence NOT IN ('p', 'u', 't') OR
     relreplident NOT IN ('d', 'n', 'f', 'i');
 
--- All tables, indexes, partitioned indexes and matviews should have an
--- access method.
+-- All tables, indexes, partitioned indexes, matviews and sequences should have
+-- an access method.
 SELECT c1.oid, c1.relname
 FROM pg_class as c1
-WHERE c1.relkind NOT IN ('S', 'v', 'f', 'c', 'p') and
+WHERE c1.relkind NOT IN ('v', 'f', 'c', 'p') and
     c1.relam = 0;
 
--- Conversely, sequences, views, foreign tables, types and partitioned
--- tables shouldn't have them.
+-- Conversely, views, foreign tables, types and partitioned tables
+-- shouldn't have them.
 SELECT c1.oid, c1.relname
 FROM pg_class as c1
-WHERE c1.relkind IN ('S', 'v', 'f', 'c', 'p') and
+WHERE c1.relkind IN ('v', 'f', 'c', 'p') and
     c1.relam != 0;
 
 -- Indexes and partitioned indexes should have AMs of type 'i'.
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 09e7f1d420ed..9f6cfb512b9f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2728,6 +2728,7 @@ SeqScanState
 SeqTable
 SeqTableData
 SeqType
+SequenceAmRoutine
 SequenceItem
 SerCommitSeqNo
 SerialControl
@@ -3816,6 +3817,7 @@ list_sort_comparator
 loc_chunk
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 local_ts_iter
 local_ts_radix_tree
@@ -4105,7 +4107,6 @@ scram_state_enum
 script_error_callback_arg
 security_class_t
 sem_t
-sepgsql_context_info_t
 sequence_magic
 set_conn_altsock_func
 set_conn_oauth_token_func
@@ -4336,6 +4337,7 @@ xl_heap_visible
 xl_invalid_page
 xl_invalid_page_key
 xl_invalidations
+xl_local_seq_rec
 xl_logical_message
 xl_multi_insert_tuple
 xl_multixact_create
@@ -4347,7 +4349,6 @@ xl_replorigin_drop
 xl_replorigin_set
 xl_restore_point
 xl_running_xacts
-xl_seq_rec
 xl_smgr_create
 xl_smgr_truncate
 xl_standby_lock
-- 
2.51.0



  [text/x-diff] v28-0004-Sequence-access-methods-dump-restore-support.patch (21.8K, 5-v28-0004-Sequence-access-methods-dump-restore-support.patch)
  download | inline diff:
From 4c6d88d61b0b7715709db0ab6e04e46476a15a08 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
Date: Fri, 26 Dec 2025 11:56:30 +0900
Subject: [PATCH v28 4/7] Sequence access methods - dump/restore support

---
 src/bin/pg_dump/pg_backup.h          |  2 +
 src/bin/pg_dump/pg_backup_archiver.c | 66 ++++++++++++++++++++++++++++
 src/bin/pg_dump/pg_backup_archiver.h |  6 ++-
 src/bin/pg_dump/pg_dump.c            | 47 +++++++++++++++-----
 src/bin/pg_dump/pg_dumpall.c         |  5 +++
 src/bin/pg_dump/pg_restore.c         |  4 ++
 src/bin/pg_dump/t/002_pg_dump.pl     | 51 +++++++++++++++++++++
 doc/src/sgml/ref/pg_dump.sgml        | 17 +++++++
 doc/src/sgml/ref/pg_dumpall.sgml     | 11 +++++
 doc/src/sgml/ref/pg_restore.sgml     | 11 +++++
 10 files changed, 209 insertions(+), 11 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index d9041dad7206..aeb75acfa3da 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -97,6 +97,7 @@ typedef struct _restoreOptions
 {
 	int			createDB;		/* Issue commands to create the database */
 	int			noOwner;		/* Don't try to match original object owner */
+	int			noSequenceAm;	/* Don't issue sequence-AM-related commands */
 	int			noTableAm;		/* Don't issue table-AM-related commands */
 	int			noTablespace;	/* Don't issue tablespace-related commands */
 	int			disable_triggers;	/* disable triggers during data-only
@@ -192,6 +193,7 @@ typedef struct _dumpOptions
 	int			no_unlogged_table_data;
 	int			serializable_deferrable;
 	int			disable_triggers;
+	int			outputNoSequenceAm;
 	int			outputNoTableAm;
 	int			outputNoTablespaces;
 	int			use_setsessauth;
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 4a63f7392ae8..dffa282b087e 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -182,6 +182,7 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt)
 	dopt->outputSuperuser = ropt->superuser;
 	dopt->outputCreateDB = ropt->createDB;
 	dopt->outputNoOwner = ropt->noOwner;
+	dopt->outputNoSequenceAm = ropt->noSequenceAm;
 	dopt->outputNoTableAm = ropt->noTableAm;
 	dopt->outputNoTablespaces = ropt->noTablespace;
 	dopt->disable_triggers = ropt->disable_triggers;
@@ -1262,6 +1263,7 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpId dumpId,
 	newToc->tag = pg_strdup(opts->tag);
 	newToc->namespace = opts->namespace ? pg_strdup(opts->namespace) : NULL;
 	newToc->tablespace = opts->tablespace ? pg_strdup(opts->tablespace) : NULL;
+	newToc->sequenceam = opts->sequenceam ? pg_strdup(opts->sequenceam) : NULL;
 	newToc->tableam = opts->tableam ? pg_strdup(opts->tableam) : NULL;
 	newToc->relkind = opts->relkind;
 	newToc->owner = opts->owner ? pg_strdup(opts->owner) : NULL;
@@ -2419,6 +2421,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 
 	AH->currUser = NULL;		/* unknown */
 	AH->currSchema = NULL;		/* ditto */
+	AH->currSequenceAm = NULL;	/* ditto */
 	AH->currTablespace = NULL;	/* ditto */
 	AH->currTableAm = NULL;		/* ditto */
 
@@ -2686,6 +2689,7 @@ WriteToc(ArchiveHandle *AH)
 		WriteStr(AH, te->copyStmt);
 		WriteStr(AH, te->namespace);
 		WriteStr(AH, te->tablespace);
+		WriteStr(AH, te->sequenceam);
 		WriteStr(AH, te->tableam);
 		WriteInt(AH, te->relkind);
 		WriteStr(AH, te->owner);
@@ -2790,6 +2794,9 @@ ReadToc(ArchiveHandle *AH)
 		if (AH->version >= K_VERS_1_10)
 			te->tablespace = ReadStr(AH);
 
+		if (AH->version >= K_VERS_1_17)
+			te->sequenceam = ReadStr(AH);
+
 		if (AH->version >= K_VERS_1_14)
 			te->tableam = ReadStr(AH);
 
@@ -3535,6 +3542,9 @@ _reconnectToDB(ArchiveHandle *AH, const char *dbname)
 	free(AH->currSchema);
 	AH->currSchema = NULL;
 
+	free(AH->currSequenceAm);
+	AH->currSequenceAm = NULL;
+
 	free(AH->currTableAm);
 	AH->currTableAm = NULL;
 
@@ -3697,6 +3707,57 @@ _selectTablespace(ArchiveHandle *AH, const char *tablespace)
 	destroyPQExpBuffer(qry);
 }
 
+/*
+ * Set the proper default_sequence_access_method value for the sequence.
+ */
+static void
+_selectSequenceAccessMethod(ArchiveHandle *AH, const char *sequenceam)
+{
+	RestoreOptions *ropt = AH->public.ropt;
+	PQExpBuffer cmd;
+	const char *want,
+			   *have;
+
+	/* do nothing in --no-sequence-access-method mode */
+	if (ropt->noSequenceAm)
+		return;
+
+	have = AH->currSequenceAm;
+	want = sequenceam;
+
+	if (!want)
+		return;
+
+	if (have && strcmp(want, have) == 0)
+		return;
+
+	cmd = createPQExpBuffer();
+	appendPQExpBuffer(cmd,
+					  "SET default_sequence_access_method = %s;",
+					  fmtId(want));
+
+	if (RestoringToDB(AH))
+	{
+		PGresult   *res;
+
+		res = PQexec(AH->connection, cmd->data);
+
+		if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
+			warn_or_exit_horribly(AH,
+								  "could not set default_sequence_access_method: %s",
+								  PQerrorMessage(AH->connection));
+
+		PQclear(res);
+	}
+	else
+		ahprintf(AH, "%s\n\n", cmd->data);
+
+	destroyPQExpBuffer(cmd);
+
+	free(AH->currSequenceAm);
+	AH->currSequenceAm = pg_strdup(want);
+}
+
 /*
  * Set the proper default_table_access_method value for the table.
  */
@@ -3906,6 +3967,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, const char *pfx)
 	_becomeOwner(AH, te);
 	_selectOutputSchema(AH, te->namespace);
 	_selectTablespace(AH, te->tablespace);
+	_selectSequenceAccessMethod(AH, te->sequenceam);
 	if (te->relkind != RELKIND_PARTITIONED_TABLE)
 		_selectTableAccessMethod(AH, te->tableam);
 
@@ -4425,6 +4487,8 @@ restore_toc_entries_prefork(ArchiveHandle *AH, TocEntry *pending_list)
 	AH->currUser = NULL;
 	free(AH->currSchema);
 	AH->currSchema = NULL;
+	free(AH->currSequenceAm);
+	AH->currSequenceAm = NULL;
 	free(AH->currTablespace);
 	AH->currTablespace = NULL;
 	free(AH->currTableAm);
@@ -5164,6 +5228,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -5223,6 +5288,7 @@ DeCloneArchive(ArchiveHandle *AH)
 	/* Clear any connection-local state */
 	free(AH->currUser);
 	free(AH->currSchema);
+	free(AH->currSequenceAm);
 	free(AH->currTablespace);
 	free(AH->currTableAm);
 	free(AH->savedPassword);
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index 325b53fc9bd4..dabab7f81f9e 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -71,10 +71,11 @@
 #define K_VERS_1_16 MAKE_ARCHIVE_VERSION(1, 16, 0)	/* BLOB METADATA entries
 													 * and multiple BLOBS,
 													 * relkind */
+#define K_VERS_1_17 MAKE_ARCHIVE_VERSION(1, 17, 0)	/* add sequenceam */
 
 /* Current archive version number (the format we can output) */
 #define K_VERS_MAJOR 1
-#define K_VERS_MINOR 16
+#define K_VERS_MINOR 17
 #define K_VERS_REV 0
 #define K_VERS_SELF MAKE_ARCHIVE_VERSION(K_VERS_MAJOR, K_VERS_MINOR, K_VERS_REV)
 
@@ -323,6 +324,7 @@ struct _archiveHandle
 	/* these vars track state to avoid sending redundant SET commands */
 	char	   *currUser;		/* current username, or NULL if unknown */
 	char	   *currSchema;		/* current schema, or NULL */
+	char	   *currSequenceAm; /* current sequence access method, or NULL */
 	char	   *currTablespace; /* current tablespace, or NULL */
 	char	   *currTableAm;	/* current table access method, or NULL */
 
@@ -358,6 +360,7 @@ struct _tocEntry
 	char	   *namespace;		/* null or empty string if not in a schema */
 	char	   *tablespace;		/* null if not in a tablespace; empty string
 								 * means use database default */
+	char	   *sequenceam;		/* table access method, only for SEQUENCE tags */
 	char	   *tableam;		/* table access method, only for TABLE tags */
 	char		relkind;		/* relation kind, only for TABLE tags */
 	char	   *owner;
@@ -403,6 +406,7 @@ typedef struct _archiveOpts
 	const char *tag;
 	const char *namespace;
 	const char *tablespace;
+	const char *sequenceam;
 	const char *tableam;
 	char		relkind;
 	const char *owner;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 7df56d8b1b0f..6202b0d16666 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -137,6 +137,7 @@ typedef struct
 	int64		cache;			/* cache size */
 	int64		last_value;		/* last value of sequence */
 	bool		is_called;		/* whether nextval advances before returning */
+	char	   *seqam;			/* access method of sequence */
 } SequenceItem;
 
 typedef enum OidOptions
@@ -504,6 +505,7 @@ main(int argc, char **argv)
 		{"if-exists", no_argument, &dopt.if_exists, 1},
 		{"inserts", no_argument, NULL, 9},
 		{"lock-wait-timeout", required_argument, NULL, 2},
+		{"no-sequence-access-method", no_argument, &dopt.outputNoSequenceAm, 1},
 		{"no-table-access-method", no_argument, &dopt.outputNoTableAm, 1},
 		{"no-tablespaces", no_argument, &dopt.outputNoTablespaces, 1},
 		{"quote-all-identifiers", no_argument, &quote_all_identifiers, 1},
@@ -1263,6 +1265,7 @@ main(int argc, char **argv)
 	ropt->superuser = dopt.outputSuperuser;
 	ropt->createDB = dopt.outputCreateDB;
 	ropt->noOwner = dopt.outputNoOwner;
+	ropt->noSequenceAm = dopt.outputNoSequenceAm;
 	ropt->noTableAm = dopt.outputNoTableAm;
 	ropt->noTablespace = dopt.outputNoTablespaces;
 	ropt->disable_triggers = dopt.disable_triggers;
@@ -1385,6 +1388,7 @@ help(const char *progname)
 	printf(_("  --no-security-labels         do not dump security label assignments\n"));
 	printf(_("  --no-statistics              do not dump statistics\n"));
 	printf(_("  --no-subscriptions           do not dump subscriptions\n"));
+	printf(_("  --no-sequence-access-method  do not dump sequence table access methods\n"));
 	printf(_("  --no-table-access-method     do not dump table access methods\n"));
 	printf(_("  --no-tablespaces             do not dump tablespace assignments\n"));
 	printf(_("  --no-toast-compression       do not dump TOAST compression methods\n"));
@@ -14437,6 +14441,9 @@ dumpAccessMethod(Archive *fout, const AccessMethodInfo *aminfo)
 		case AMTYPE_INDEX:
 			appendPQExpBufferStr(q, "TYPE INDEX ");
 			break;
+		case AMTYPE_SEQUENCE:
+			appendPQExpBufferStr(q, "TYPE SEQUENCE ");
+			break;
 		case AMTYPE_TABLE:
 			appendPQExpBufferStr(q, "TYPE TABLE ");
 			break;
@@ -18920,26 +18927,40 @@ collectSequences(Archive *fout)
 	 *
 	 * Since version 18, we can gather the sequence data in this query with
 	 * pg_get_sequence_data(), but we only do so for non-schema-only dumps.
+	 *
+	 * Access methods for sequences are supported since version 18.
 	 */
 	if (fout->remoteVersion < 100000)
 		return;
-	else if (fout->remoteVersion < 180000 ||
-			 (!fout->dopt->dumpData && !fout->dopt->sequence_data))
+	else if (fout->remoteVersion < 180000)
 		query = "SELECT seqrelid, format_type(seqtypid, NULL), "
 			"seqstart, seqincrement, "
 			"seqmax, seqmin, "
 			"seqcache, seqcycle, "
-			"NULL, 'f' "
+			"NULL, 'f', NULL "
 			"FROM pg_catalog.pg_sequence "
 			"ORDER BY seqrelid";
+	else if	(!fout->dopt->dumpData && !fout->dopt->sequence_data)
+		query = "SELECT s.seqrelid, format_type(s.seqtypid, NULL), "
+			"s.seqstart, s.seqincrement, "
+			"s.seqmax, s.seqmin, "
+			"s.seqcache, s.seqcycle, "
+			"NULL, 'f', a.amname AS seqam "
+			"FROM pg_catalog.pg_sequence s "
+			"JOIN pg_class c ON (c.oid = s.seqrelid) "
+			"JOIN pg_am a ON (a.oid = c.relam) "
+			"ORDER BY seqrelid";
 	else
-		query = "SELECT seqrelid, format_type(seqtypid, NULL), "
-			"seqstart, seqincrement, "
-			"seqmax, seqmin, "
-			"seqcache, seqcycle, "
-			"last_value, is_called "
-			"FROM pg_catalog.pg_sequence, "
-			"pg_get_sequence_data(seqrelid) "
+		query = "SELECT s.seqrelid, format_type(s.seqtypid, NULL), "
+			"s.seqstart, s.seqincrement, "
+			"s.seqmax, s.seqmin, "
+			"s.seqcache, s.seqcycle, "
+			"r.last_value, r.is_called, "
+			"a.amname AS seqam "
+			"FROM pg_catalog.pg_sequence s "
+			"JOIN pg_class c ON (c.oid = s.seqrelid) "
+			"JOIN pg_am a ON (a.oid = c.relam), "
+			"pg_get_sequence_data(s.seqrelid) r "
 			"ORDER BY seqrelid;";
 
 	res = ExecuteSqlQuery(fout, query, PGRES_TUPLES_OK);
@@ -18959,6 +18980,10 @@ collectSequences(Archive *fout)
 		sequences[i].cycled = (strcmp(PQgetvalue(res, i, 7), "t") == 0);
 		sequences[i].last_value = strtoi64(PQgetvalue(res, i, 8), NULL, 10);
 		sequences[i].is_called = (strcmp(PQgetvalue(res, i, 9), "t") == 0);
+		if (!PQgetisnull(res, i, 10))
+			sequences[i].seqam = pg_strdup(PQgetvalue(res, i, 10));
+		else
+			sequences[i].seqam = NULL;
 	}
 
 	PQclear(res);
@@ -19030,6 +19055,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 		seq->minv = strtoi64(PQgetvalue(res, 0, 4), NULL, 10);
 		seq->cache = strtoi64(PQgetvalue(res, 0, 5), NULL, 10);
 		seq->cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
+		seq->seqam = NULL;
 
 		PQclear(res);
 	}
@@ -19152,6 +19178,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 					 ARCHIVE_OPTS(.tag = tbinfo->dobj.name,
 								  .namespace = tbinfo->dobj.namespace->dobj.name,
 								  .owner = tbinfo->rolname,
+								  .sequenceam = seq->seqam,
 								  .description = "SEQUENCE",
 								  .section = SECTION_PRE_DATA,
 								  .createStmt = query->data,
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index e85f227d1826..88b0a6aa2a94 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -91,6 +91,7 @@ static int	disable_dollar_quoting = 0;
 static int	disable_triggers = 0;
 static int	if_exists = 0;
 static int	inserts = 0;
+static int	no_sequence_access_method = 0;
 static int	no_table_access_method = 0;
 static int	no_tablespaces = 0;
 static int	use_setsessauth = 0;
@@ -162,6 +163,7 @@ main(int argc, char *argv[])
 		{"if-exists", no_argument, &if_exists, 1},
 		{"inserts", no_argument, &inserts, 1},
 		{"lock-wait-timeout", required_argument, NULL, 2},
+		{"no-sequence-access-method", no_argument, &no_sequence_access_method, 1},
 		{"no-table-access-method", no_argument, &no_table_access_method, 1},
 		{"no-tablespaces", no_argument, &no_tablespaces, 1},
 		{"quote-all-identifiers", no_argument, &quote_all_identifiers, 1},
@@ -456,6 +458,8 @@ main(int argc, char *argv[])
 		appendPQExpBufferStr(pgdumpopts, " --disable-triggers");
 	if (inserts)
 		appendPQExpBufferStr(pgdumpopts, " --inserts");
+	if (no_sequence_access_method)
+		appendPQExpBufferStr(pgdumpopts, " --no-sequence-access-method");
 	if (no_table_access_method)
 		appendPQExpBufferStr(pgdumpopts, " --no-table-access-method");
 	if (no_tablespaces)
@@ -737,6 +741,7 @@ help(void)
 	printf(_("  --no-statistics              do not dump statistics\n"));
 	printf(_("  --no-subscriptions           do not dump subscriptions\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
+	printf(_("  --no-sequence-access-method  do not dump sequence access methods\n"));
 	printf(_("  --no-table-access-method     do not dump table access methods\n"));
 	printf(_("  --no-tablespaces             do not dump tablespace assignments\n"));
 	printf(_("  --no-toast-compression       do not dump TOAST compression methods\n"));
diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c
index 84b8d410c9ef..e20f93eb94a6 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -70,6 +70,7 @@ main(int argc, char **argv)
 	static int	enable_row_security = 0;
 	static int	if_exists = 0;
 	static int	no_data_for_failed_tables = 0;
+	static int	outputNoSequenceAm = 0;
 	static int	outputNoTableAm = 0;
 	static int	outputNoTablespaces = 0;
 	static int	use_setsessauth = 0;
@@ -123,6 +124,7 @@ main(int argc, char **argv)
 		{"enable-row-security", no_argument, &enable_row_security, 1},
 		{"if-exists", no_argument, &if_exists, 1},
 		{"no-data-for-failed-tables", no_argument, &no_data_for_failed_tables, 1},
+		{"no-sequence-access-method", no_argument, &outputNoSequenceAm, 1},
 		{"no-table-access-method", no_argument, &outputNoTableAm, 1},
 		{"no-tablespaces", no_argument, &outputNoTablespaces, 1},
 		{"role", required_argument, NULL, 2},
@@ -447,6 +449,7 @@ main(int argc, char **argv)
 	opts->disable_triggers = disable_triggers;
 	opts->enable_row_security = enable_row_security;
 	opts->noDataForFailedTables = no_data_for_failed_tables;
+	opts->noSequenceAm = outputNoSequenceAm;
 	opts->noTableAm = outputNoTableAm;
 	opts->noTablespace = outputNoTablespaces;
 	opts->use_setsessauth = use_setsessauth;
@@ -579,6 +582,7 @@ usage(const char *progname)
 	printf(_("  --no-security-labels         do not restore security labels\n"));
 	printf(_("  --no-statistics              do not restore statistics\n"));
 	printf(_("  --no-subscriptions           do not restore subscriptions\n"));
+	printf(_("  --no-sequence-access-method     do not restore sequence access methods\n"));
 	printf(_("  --no-table-access-method     do not restore table access methods\n"));
 	printf(_("  --no-tablespaces             do not restore tablespace assignments\n"));
 	printf(_("  --restrict-key=RESTRICT_KEY  use provided string as psql \\restrict key\n"));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 28812d28aa9a..f2c2b08ba894 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -414,6 +414,15 @@ my %pgdump_runs = (
 			'postgres',
 		],
 	},
+	no_sequence_access_method => {
+		dump_cmd => [
+			'pg_dump', '--no-sync',
+			"--file=$tempdir/no_sequence_access_method.sql",
+			'--no-sequence-access-method',
+			'--statistics',
+			'postgres',
+		],
+	},
 	no_subscriptions => {
 		dump_cmd => [
 			'pg_dump', '--no-sync',
@@ -659,6 +668,7 @@ my %full_runs = (
 	no_policies_restore => 1,
 	no_privs => 1,
 	no_statistics => 1,
+	no_sequence_access_method => 1,
 	no_subscriptions => 1,
 	no_subscriptions_restore => 1,
 	no_table_access_method => 1,
@@ -4670,6 +4680,18 @@ my %tests = (
 		},
 	},
 
+	'CREATE ACCESS METHOD regress_test_sequence_am' => {
+		create_order => 11,
+		create_sql =>
+		  'CREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER seq_local_sequenceam_handler;',
+		regexp => qr/^
+			\QCREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER seq_local_sequenceam_handler;\E
+			\n/xm,
+		like => {
+			%full_runs, section_pre_data => 1,
+		},
+	},
+
 	# It's a bit tricky to ensure that the proper SET of default table
 	# AM occurs. To achieve that we create a table with the standard
 	# AM, test AM, standard AM. That guarantees that there needs to be
@@ -4698,6 +4720,35 @@ my %tests = (
 		},
 	},
 
+
+	# This uses the same trick as for materialized views and tables,
+	# but this time with a sequence access method, checking that a
+	# correct set of SET queries are created.
+	'CREATE SEQUENCE regress_pg_dump_seq_am' => {
+		create_order => 12,
+		create_sql => '
+			CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_0 USING seqlocal;
+			CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_1 USING regress_sequence_am;
+			CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_2 USING seqlocal;',
+		regexp => qr/^
+			\QSET default_sequence_access_method = regress_sequence_am;\E
+			(\n(?!SET[^;]+;)[^\n]*)*
+			\n\QCREATE SEQUENCE dump_test.regress_pg_dump_seq_am_1\E
+			\n\s+\QSTART WITH 1\E
+			\n\s+\QINCREMENT BY 1\E
+			\n\s+\QNO MINVALUE\E
+			\n\s+\QNO MAXVALUE\E
+			\n\s+\QCACHE 1;\E\n/xm,
+		like => {
+			%full_runs, %dump_test_schema_runs, section_pre_data => 1,
+		},
+		unlike => {
+			exclude_dump_test_schema => 1,
+			no_sequence_access_method => 1,
+			only_dump_measurement => 1,
+		},
+	},
+
 	'CREATE MATERIALIZED VIEW regress_pg_dump_matview_am' => {
 		create_order => 13,
 		create_sql => '
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 688e23c0e908..c94890458bb3 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1174,6 +1174,23 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--no-sequence-access-method</option></term>
+      <listitem>
+       <para>
+        Do not output commands to select sequence access methods.
+        With this option, all objects will be created with whichever
+        sequence access method is the default during restore.
+       </para>
+
+       <para>
+        This option is ignored when emitting an archive (non-text) output
+        file.  For the archive formats, you can specify the option when you
+        call <command>pg_restore</command>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--no-table-access-method</option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml
index 8834b7ec141e..9939f096572c 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -525,6 +525,17 @@ exclude database <replaceable class="parameter">PATTERN</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--no-sequence-access-method</option></term>
+      <listitem>
+       <para>
+        Do not output commands to select sequence access methods.
+        With this option, all objects will be created with whichever
+        sequence access method is the default during restore.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--no-table-access-method</option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml
index a468a38361a1..2a8ed492b216 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -786,6 +786,17 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--no-sequence-access-method</option></term>
+      <listitem>
+       <para>
+        Do not output commands to select sequence access methods.
+        With this option, all objects will be created with whichever
+        sequence access method is the default during restore.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--no-table-access-method</option></term>
       <listitem>
-- 
2.51.0



  [text/x-diff] v28-0005-Sequence-access-methods-core-documentation.patch (9.5K, 6-v28-0005-Sequence-access-methods-core-documentation.patch)
  download | inline diff:
From 7df3c23b7bab7ae09187e86f83d6a206982f7ca4 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
Date: Fri, 26 Dec 2025 12:05:36 +0900
Subject: [PATCH v28 5/7] Sequence access methods - core documentation

---
 doc/src/sgml/config.sgml                   | 16 +++++
 doc/src/sgml/filelist.sgml                 |  1 +
 doc/src/sgml/postgres.sgml                 |  1 +
 doc/src/sgml/ref/create_access_method.sgml | 15 ++--
 doc/src/sgml/ref/create_sequence.sgml      | 12 ++++
 doc/src/sgml/sequenceam.sgml               | 79 ++++++++++++++++++++++
 6 files changed, 118 insertions(+), 6 deletions(-)
 create mode 100644 doc/src/sgml/sequenceam.sgml

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 0fad34da6eb0..e0af7edf4409 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9856,6 +9856,22 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-default-sequence-access-method" xreflabel="default_sequence_access_method">
+      <term><varname>default_sequence_access_method</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>default_sequence_access_method</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        This parameter specifies the default sequence access method to use when
+        creating sequences if the <command>CREATE SEQUENCE</command>
+        command does not explicitly specify an access method. The default is
+        <literal>local</literal>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-default-tablespace" xreflabel="default_tablespace">
       <term><varname>default_tablespace</varname> (<type>string</type>)
       <indexterm>
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index ac66fcbdb572..2dd792228a57 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -98,6 +98,7 @@
 <!ENTITY planstats    SYSTEM "planstats.sgml">
 <!ENTITY tableam    SYSTEM "tableam.sgml">
 <!ENTITY indexam    SYSTEM "indexam.sgml">
+<!ENTITY sequenceam SYSTEM "sequenceam.sgml">
 <!ENTITY nls        SYSTEM "nls.sgml">
 <!ENTITY plhandler  SYSTEM "plhandler.sgml">
 <!ENTITY fdwhandler SYSTEM "fdwhandler.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2101442c90fc..9ceed4064a45 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -258,6 +258,7 @@ break is not needed in a wider output rendering.
   &geqo;
   &tableam;
   &indexam;
+  &sequenceam;
   &wal-for-extensions;
   &indextypes;
   &storage;
diff --git a/doc/src/sgml/ref/create_access_method.sgml b/doc/src/sgml/ref/create_access_method.sgml
index dae43dbaed58..905f2be9933f 100644
--- a/doc/src/sgml/ref/create_access_method.sgml
+++ b/doc/src/sgml/ref/create_access_method.sgml
@@ -61,8 +61,8 @@ CREATE ACCESS METHOD <replaceable class="parameter">name</replaceable>
     <listitem>
      <para>
       This clause specifies the type of access method to define.
-      Only <literal>TABLE</literal> and <literal>INDEX</literal>
-      are supported at present.
+      Only <literal>TABLE</literal>, <literal>INDEX</literal> and
+      <literal>SEQUENCE</literal> are supported at present.
      </para>
     </listitem>
    </varlistentry>
@@ -77,12 +77,15 @@ CREATE ACCESS METHOD <replaceable class="parameter">name</replaceable>
       declared to take a single argument of type <type>internal</type>,
       and its return type depends on the type of access method;
       for <literal>TABLE</literal> access methods, it must
-      be <type>table_am_handler</type> and for <literal>INDEX</literal>
-      access methods, it must be <type>index_am_handler</type>.
+      be <type>table_am_handler</type>; for <literal>INDEX</literal>
+      access methods, it must be <type>index_am_handler</type>;
+      for <literal>SEQUENCE</literal>, it must be
+      <type>sequence_am_handler</type>;
       The C-level API that the handler function must implement varies
       depending on the type of access method. The table access method API
-      is described in <xref linkend="tableam"/> and the index access method
-      API is described in <xref linkend="indexam"/>.
+      is described in <xref linkend="tableam"/>, the index access method
+      API is described in <xref linkend="indexam"/> and the sequence access
+      method is described in <xref linkend="sequenceam"/>.
      </para>
     </listitem>
    </varlistentry>
diff --git a/doc/src/sgml/ref/create_sequence.sgml b/doc/src/sgml/ref/create_sequence.sgml
index 0ffcd0febd1b..e89ffbbcf908 100644
--- a/doc/src/sgml/ref/create_sequence.sgml
+++ b/doc/src/sgml/ref/create_sequence.sgml
@@ -29,6 +29,7 @@ CREATE [ { TEMPORARY | TEMP } | UNLOGGED ] SEQUENCE [ IF NOT EXISTS ] <replaceab
     [ START [ WITH ] <replaceable class="parameter">start</replaceable> ]
     [ CACHE <replaceable class="parameter">cache</replaceable> ]
     [ OWNED BY { <replaceable class="parameter">table_name</replaceable>.<replaceable class="parameter">column_name</replaceable> | NONE } ]
+    [ USING <replaceable class="parameter">access_method</replaceable> ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -263,6 +264,17 @@ SELECT * FROM <replaceable>name</replaceable>;
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>USING</literal> <replaceable class="parameter">access_method</replaceable></term>
+    <listitem>
+     <para>
+      The <literal>USING</literal> option specifies which sequence access
+      method will be used when generating the sequence numbers. The default
+      is <literal>local</literal>.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/sequenceam.sgml b/doc/src/sgml/sequenceam.sgml
new file mode 100644
index 000000000000..22ee3a4bfa01
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,79 @@
+<!-- doc/src/sgml/sequenceam.sgml -->
+
+<chapter id="sequenceam">
+ <title>Sequence Access Method Interface Definition</title>
+
+ <indexterm>
+  <primary>Sequence Access Method</primary>
+ </indexterm>
+ <indexterm>
+  <primary>sequenceam</primary>
+  <secondary>Sequence Access Method</secondary>
+ </indexterm>
+
+ <para>
+  This chapter explains the interface between the core
+  <productname>PostgreSQL</productname> system and <firstterm>sequence access
+  methods</firstterm>, which manage the operations around sequences. The core
+  system knows little about these access methods beyond what is specified here,
+  so it is possible to develop new access methods by writing add-on code.
+ </para>
+
+ <para>
+  Each sequence access method is described by a row in the
+  <link linkend="catalog-pg-am"><structname>pg_am</structname></link> system
+  catalog. The <structname>pg_am</structname> entry specifies a name and a
+  <firstterm>handler function</firstterm> for the sequence access method.  These
+  entries can be created and deleted using the
+  <xref linkend="sql-create-access-method"/> and
+  <xref linkend="sql-drop-access-method"/> SQL commands.
+ </para>
+
+ <para>
+  A sequence access method handler function must be declared to accept a single
+  argument of type <type>internal</type> and to return the pseudo-type
+  <type>sequence_am_handler</type>.  The argument is a dummy value that simply
+  serves to prevent handler functions from being called directly from SQL commands.
+
+  The result of the function must be a pointer to a struct of type
+  <structname>SequenceAmRoutine</structname>, which contains everything that the
+  core code needs to know to make use of the sequence access method. The return
+  value needs to be of server lifetime, which is typically achieved by
+  defining it as a <literal>static const</literal> variable in global
+  scope. The <structname>SequenceAmRoutine</structname> struct, also called the
+  access method's <firstterm>API struct</firstterm>, defines the behavior of
+  the access method using callbacks. These callbacks are pointers to plain C
+  functions and are not visible or callable at the SQL level. All the
+  callbacks and their behavior is defined in the
+  <structname>SequenceAmRoutine</structname> structure (with comments inside
+  the struct defining the requirements for callbacks). Most callbacks have
+  wrapper functions, which are documented from the point of view of a user
+  (rather than an implementor) of the sequence access method.  For details,
+  please refer to the <ulink url="https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/include/access/sequenceam.h;hb=HEAD">
+   <filename>src/include/access/sequenceam.h</filename></ulink> file.
+ </para>
+
+ <para>
+  Currently, the way a sequence access method stores data is fairly
+  unconstrained, and it is possible to use a predefined
+  <link linkend="tableam">Table Access Method</link> to store sequence
+  data.
+ </para>
+
+ <para>
+  For crash safety, a sequence access method can use
+  <link linkend="wal"><acronym>WAL</acronym></link>, or a custom
+  implementation.
+  If <acronym>WAL</acronym> is chosen, either
+  <link linkend="generic-wal">Generic WAL Records</link> can be used, or a
+  <link linkend="custom-rmgr">Custom WAL Resource Manager</link> can be
+  implemented.
+ </para>
+
+ <para>
+  Any developer of a new <literal>sequence access method</literal> can refer to
+  the existing <literal>seqlocal</literal> implementation present in
+  <filename>src/backend/access/sequence/seqlocalam.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.51.0



  [text/x-diff] v28-0006-Refactor-logic-for-page-manipulations-of-sequenc.patch (7.8K, 7-v28-0006-Refactor-logic-for-page-manipulations-of-sequenc.patch)
  download | inline diff:
From 26e42deeea1a773ce543429d000953590e689256 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
Date: Thu, 21 Aug 2025 11:29:39 +0900
Subject: [PATCH v28 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 2f0c3b5b4dda..ced1c38978a9 100644
--- a/src/backend/access/sequence/seqlocalam.c
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -19,6 +19,7 @@
 #include "access/multixact.h"
 #include "access/seqlocal_xlog.h"
 #include "access/sequenceam.h"
+#include "access/sequence_page.h"
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
@@ -77,27 +78,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
@@ -116,8 +101,6 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 		MarkBufferDirtyHint(*buf, true);
 	}
 
-	seq = (Form_pg_seq_local_data) GETSTRUCT(seqdatatuple);
-
 	return seq;
 }
 
@@ -156,33 +139,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



  [text/x-diff] v28-0007-snowflake-Add-sequence-AM-based-on-it.patch (25.9K, 8-v28-0007-snowflake-Add-sequence-AM-based-on-it.patch)
  download | inline diff:
From cf3a86b1d0920fe3eb50e17e809db3bf7c134ab6 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
Date: Wed, 7 Jan 2026 14:24:50 +0900
Subject: [PATCH v28 7/7] snowflake: Add sequence AM based on it

This includes documentation and a basic implementation, ready to use.
---
 doc/src/sgml/contrib.sgml                |   1 +
 doc/src/sgml/filelist.sgml               |   1 +
 doc/src/sgml/snowflake.sgml              | 100 +++++
 contrib/Makefile                         |   1 +
 contrib/meson.build                      |   1 +
 contrib/snowflake/.gitignore             |   3 +
 contrib/snowflake/Makefile               |  19 +
 contrib/snowflake/expected/snowflake.out |  73 ++++
 contrib/snowflake/meson.build            |  34 ++
 contrib/snowflake/snowflake--1.0.sql     |  21 +
 contrib/snowflake/snowflake.c            | 519 +++++++++++++++++++++++
 contrib/snowflake/snowflake.control      |   5 +
 contrib/snowflake/sql/snowflake.sql      |  29 ++
 13 files changed, 807 insertions(+)
 create mode 100644 doc/src/sgml/snowflake.sgml
 create mode 100644 contrib/snowflake/.gitignore
 create mode 100644 contrib/snowflake/Makefile
 create mode 100644 contrib/snowflake/expected/snowflake.out
 create mode 100644 contrib/snowflake/meson.build
 create mode 100644 contrib/snowflake/snowflake--1.0.sql
 create mode 100644 contrib/snowflake/snowflake.c
 create mode 100644 contrib/snowflake/snowflake.control
 create mode 100644 contrib/snowflake/sql/snowflake.sql

diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index 24b706b29adc..5e26d5baacc9 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -168,6 +168,7 @@ CREATE EXTENSION <replaceable>extension_name</replaceable>;
  &seg;
  &sepgsql;
  &contrib-spi;
+ &snowflake;
  &sslinfo;
  &tablefunc;
  &tcn;
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 2dd792228a57..fe278a4ed550 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -162,6 +162,7 @@
 <!ENTITY seg             SYSTEM "seg.sgml">
 <!ENTITY contrib-spi     SYSTEM "contrib-spi.sgml">
 <!ENTITY sepgsql         SYSTEM "sepgsql.sgml">
+<!ENTITY snowflake       SYSTEM "snowflake.sgml">
 <!ENTITY sslinfo         SYSTEM "sslinfo.sgml">
 <!ENTITY tablefunc       SYSTEM "tablefunc.sgml">
 <!ENTITY tcn             SYSTEM "tcn.sgml">
diff --git a/doc/src/sgml/snowflake.sgml b/doc/src/sgml/snowflake.sgml
new file mode 100644
index 000000000000..060699e7ecd1
--- /dev/null
+++ b/doc/src/sgml/snowflake.sgml
@@ -0,0 +1,100 @@
+<!-- doc/src/sgml/snowflake.sgml -->
+
+<sect1 id="snowflake" xreflabel="snowflake">
+ <title>snowflake &mdash; sequence access method</title>
+
+ <indexterm zone="snowflake">
+  <primary>snowflake</primary>
+ </indexterm>
+
+ <para>
+  <literal>snowflake</literal> provides a sequence access method based on
+  <ulink url="https://en.wikipedia.org/wiki/Snowflake_ID">Snowflake IDs</ulink>.
+ </para>
+
+ <para>
+  A Snowflake ID (or snowflake) is a unique 64-bit identifier made of three
+  components:
+  <itemizedlist spacing="compact">
+   <listitem><para>41 bits for a timestamp, epoch-adjusted in milli-seconds</para></listitem>
+   <listitem><para>10 bits for machine ID</para></listitem>
+   <listitem><para>12 bits for a sequence number</para></listitem>
+  </itemizedlist>
+ </para>
+
+ <sect2 id="snowflake-functions">
+  <title>Functions</title>
+
+  <variablelist>
+   <varlistentry>
+    <term>
+     <function>snowflake_get(raw int8) returns record</function>
+     <indexterm>
+      <primary>snowflake_get</primary>
+      <secondary>function</secondary>
+     </indexterm>
+    </term>
+
+    <listitem>
+     <para>
+      Returns a record made of the timestamp in milli-seconds, the machine ID
+      and the sequence number for a single snowflake ID.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </sect2>
+
+ <sect2 id="snowflake-parameters">
+  <title>Configuration Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term>
+     <varname>snowflake.machine_id</varname>
+     <indexterm>
+      <primary><varname>snowflake.machine_id</varname> configuration parameter</primary>
+     </indexterm>
+    </term>
+    <listitem>
+     <para>
+      Machine ID assigned to the snowflake IDs used in the sequence. The
+      default value is <literal>1</literal>.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </sect2>
+
+ <sect2 id="snowflake-examples">
+  <title>Examples</title>
+
+  <para>
+   This is an example of creating a snowflake sequence:
+  </para>
+
+<programlisting>
+CREATE SEQUENCE snowflake_seq USING snowflake;
+</programlisting>
+
+  <para>
+   Similarly to the default sequence access method, snowflake sequences
+   can be queried as a table:
+  </para>
+
+<programlisting>
+ =# SELECT * FROM snowflake_seq;
+ count | is_called
+-------+-----------
+     1 | f
+(1 row)
+=# SELECT to_timestamp(time_ms / 1000), machine, counter
+     FROM snowflake_get(nextval('snowflake_seq'));
+      to_timestamp      | machine | counter
+------------------------+---------+---------
+ 2024-04-26 14:28:26+09 |       1 |       3
+(1 row)
+</programlisting>
+ </sect2>
+
+</sect1>
diff --git a/contrib/Makefile b/contrib/Makefile
index 2f0a88d3f774..26cf6e94ff10 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -44,6 +44,7 @@ SUBDIRS = \
 		pg_walinspect	\
 		postgres_fdw	\
 		seg		\
+		snowflake	\
 		spi		\
 		tablefunc	\
 		tcn		\
diff --git a/contrib/meson.build b/contrib/meson.build
index def13257cbea..3b89a280779d 100644
--- a/contrib/meson.build
+++ b/contrib/meson.build
@@ -59,6 +59,7 @@ subdir('pg_walinspect')
 subdir('postgres_fdw')
 subdir('seg')
 subdir('sepgsql')
+subdir('snowflake')
 subdir('spi')
 subdir('sslinfo')
 # start-scripts doesn't contain build products
diff --git a/contrib/snowflake/.gitignore b/contrib/snowflake/.gitignore
new file mode 100644
index 000000000000..44d119cfcc24
--- /dev/null
+++ b/contrib/snowflake/.gitignore
@@ -0,0 +1,3 @@
+# Generated subdirectories
+/log/
+/results/
diff --git a/contrib/snowflake/Makefile b/contrib/snowflake/Makefile
new file mode 100644
index 000000000000..fa5b48d565d8
--- /dev/null
+++ b/contrib/snowflake/Makefile
@@ -0,0 +1,19 @@
+# contrib/snowflake/Makefile
+
+MODULES = snowflake
+
+EXTENSION = snowflake
+DATA = snowflake--1.0.sql
+
+REGRESS = snowflake
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/snowflake
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/snowflake/expected/snowflake.out b/contrib/snowflake/expected/snowflake.out
new file mode 100644
index 000000000000..b7e469bf7384
--- /dev/null
+++ b/contrib/snowflake/expected/snowflake.out
@@ -0,0 +1,73 @@
+CREATE EXTENSION snowflake;
+CREATE SEQUENCE snowflake_seq USING snowflake;
+SET snowflake.machine_id = 2000; -- error
+ERROR:  2000 is outside the valid range for parameter "snowflake.machine_id" (0 .. 1023)
+SET snowflake.machine_id = 4; -- ok
+SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq'));
+ machine | counter 
+---------+---------
+       4 |       2
+(1 row)
+
+SELECT machine, counter FROM snowflake_get(lastval());
+ machine | counter 
+---------+---------
+       4 |       2
+(1 row)
+
+SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq'));
+ machine | counter 
+---------+---------
+       4 |       3
+(1 row)
+
+SELECT machine, counter FROM snowflake_get(currval('snowflake_seq'));
+ machine | counter 
+---------+---------
+       4 |       3
+(1 row)
+
+-- Sequence relation exists, is unlogged and remains unlogged.
+SELECT * FROM snowflake_seq;
+ count | is_called 
+-------+-----------
+     3 | t
+(1 row)
+
+ALTER SEQUENCE snowflake_seq SET LOGGED;
+SELECT relpersistence FROM pg_class where relname = 'snowflake_seq';
+ relpersistence 
+----------------
+ u
+(1 row)
+
+ALTER SEQUENCE snowflake_seq RESTART;
+SELECT * FROM snowflake_seq;
+ count | is_called 
+-------+-----------
+     1 | f
+(1 row)
+
+-- Identity column, where cache affects value.
+SET default_sequence_access_method = 'snowflake';
+CREATE TABLE snowflake_tab (a int GENERATED ALWAYS AS IDENTITY, b int);
+INSERT INTO snowflake_tab VALUES (DEFAULT, generate_series(1, 10));
+SELECT data.machine, data.counter
+  FROM snowflake_tab, LATERAL snowflake_get(a) AS data;
+ machine | counter 
+---------+---------
+       4 |       2
+       4 |       3
+       4 |       4
+       4 |       5
+       4 |       6
+       4 |       7
+       4 |       8
+       4 |       9
+       4 |      10
+       4 |      11
+(10 rows)
+
+DROP TABLE snowflake_tab;
+DROP SEQUENCE snowflake_seq;
+DROP EXTENSION snowflake;
diff --git a/contrib/snowflake/meson.build b/contrib/snowflake/meson.build
new file mode 100644
index 000000000000..d05298601179
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+snowflake_sources = files(
+  'snowflake.c',
+)
+
+if host_system == 'windows'
+  snowflake_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'snowflake',
+    '--FILEDESC', 'snowflake - sequence access method',])
+endif
+
+snowflake = shared_module('snowflake',
+  snowflake_sources,
+  kwargs: contrib_mod_args,
+)
+contrib_targets += snowflake
+
+install_data(
+  'snowflake.control',
+  'snowflake--1.0.sql',
+  kwargs: contrib_data_args,
+)
+
+tests += {
+  'name': 'snowflake',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'regress': {
+    'sql': [
+      'snowflake',
+    ],
+  },
+}
diff --git a/contrib/snowflake/snowflake--1.0.sql b/contrib/snowflake/snowflake--1.0.sql
new file mode 100644
index 000000000000..bcb9d754f1b4
--- /dev/null
+++ b/contrib/snowflake/snowflake--1.0.sql
@@ -0,0 +1,21 @@
+/* contrib/snowflake/snowflake--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION snowflake" to load this file. \quit
+
+CREATE FUNCTION snowflake_sequenceam_handler(internal)
+  RETURNS sequence_am_handler
+  AS 'MODULE_PATHNAME'
+  LANGUAGE C;
+
+CREATE ACCESS METHOD snowflake
+  TYPE SEQUENCE HANDLER snowflake_sequenceam_handler;
+COMMENT ON ACCESS METHOD snowflake IS 'snowflake sequence access method';
+
+CREATE FUNCTION snowflake_get(IN raw int8,
+    OUT time_ms int8,
+    OUT machine int4,
+    OUT counter int4)
+  RETURNS record
+  AS 'MODULE_PATHNAME'
+  LANGUAGE C STRICT
diff --git a/contrib/snowflake/snowflake.c b/contrib/snowflake/snowflake.c
new file mode 100644
index 000000000000..513739f056f4
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,519 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  contrib/snowflake/snowflake.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/time.h>
+
+#include "access/generic_xlog.h"
+#include "access/htup_details.h"
+#include "access/sequenceam.h"
+#include "access/sequence_page.h"
+#include "access/xact.h"
+#include "catalog/storage_xlog.h"
+#include "commands/tablecmds.h"
+#include "fmgr.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "storage/bufmgr.h"
+#include "utils/guc.h"
+#include "utils/numeric.h"
+
+PG_MODULE_MAGIC;
+
+/* "special area" of a snowflake's buffer page. */
+#define SNOWFLAKE_MAGIC	0x01
+
+typedef struct snowflake_magic
+{
+	uint32		magic;
+} snowflake_magic;
+
+/* -----------------------------------------------------------------------
+ * Snowflake ID are 64-bit based, with the following structure:
+ * - 41 bits for an epoch-based timestamp, in milli-seconds.
+ * - 10 bits for a machine ID.
+ * - 12 bits for a sequence counter.
+ *
+ * The timestamp can be cut to an offset.  The machine ID is controlled
+ * by a superuser GUC.  Sequence properties apply to the sequence counter,
+ * as the other two are environment-dependent.
+ * -----------------------------------------------------------------------
+ */
+
+/*
+ * Helper routines to convert a snowflake ID from/to an int64.
+ */
+#define SNOWFLAKE_COUNTER_MASK		0x0000000000000FFF	/* 12 bits */
+#define SNOWFLAKE_COUNTER_SHIFT		0
+#define SNOWFLAKE_MACHINE_ID_MASK	0x00000000000003FF	/* 10 bits */
+#define SNOWFLAKE_MACHINE_ID_SHIFT	12	/* counter */
+#define SNOWFLAKE_TIMESTAMP_MASK	0x000001FFFFFFFFFF	/* 41 bits */
+#define SNOWFLAKE_TIMESTAMP_SHIFT	22	/* machine ID + counter sizes */
+
+typedef struct snowflake_id
+{
+	uint32		count;
+	uint32		machine;
+	uint64		time_ms;
+} snowflake_id;
+
+#define snowflake_id_to_int64(id, raw) { \
+	raw = (((id).count) & SNOWFLAKE_COUNTER_MASK) << SNOWFLAKE_COUNTER_SHIFT |	\
+		(((id).machine) & SNOWFLAKE_MACHINE_ID_MASK) << SNOWFLAKE_MACHINE_ID_SHIFT | \
+		(((id).time_ms) & SNOWFLAKE_TIMESTAMP_MASK) << SNOWFLAKE_TIMESTAMP_SHIFT;	\
+}
+
+#define int64_to_snowflake_id(raw, id) { \
+	(id).count = ((raw) >> SNOWFLAKE_COUNTER_SHIFT) & SNOWFLAKE_COUNTER_MASK;		\
+	(id).machine = ((raw) >> SNOWFLAKE_MACHINE_ID_SHIFT) & SNOWFLAKE_MACHINE_ID_MASK; \
+	(id).time_ms = ((raw) >> SNOWFLAKE_TIMESTAMP_SHIFT) & SNOWFLAKE_TIMESTAMP_MASK;	\
+}
+
+/*
+ * Format of tuples stored in heap table associated to snowflake sequence.
+ */
+typedef struct FormData_snowflake_data
+{
+	/* enough to cover 12 bits of the internal counter */
+	int16	count;
+	bool	is_called;
+} FormData_snowflake_data;
+
+typedef FormData_snowflake_data *Form_snowflake_data;
+
+/*
+ * Columns of a snowflake sequence relation.
+ */
+#define SNOWFLAKE_COL_COUNT		1
+#define SNOWFLAKE_COL_CALLED	2
+
+#define	SNOWFLAKE_COLS			2
+
+/* GUC parameter */
+static int snowflake_machine_id = 1;
+
+PG_FUNCTION_INFO_V1(snowflake_sequenceam_handler);
+
+/* -----------------------------------------------------------------------
+ * Interfaces for relation manipulation.
+ * -----------------------------------------------------------------------
+ */
+
+/*
+ * Initialize snowflake relation's fork with some data.
+ */
+static void
+fill_snowflake_fork(Relation rel, HeapTuple tuple, ForkNumber forkNum)
+{
+	Buffer		buf;
+	Page		page;
+	snowflake_magic *sm;
+	OffsetNumber offnum;
+	GenericXLogState *state = NULL;
+
+	/* Initialize first page of relation */
+	SEQUENCE_PAGE_INIT(snowflake_magic, SNOWFLAKE_MAGIC);
+
+	/*
+	 * Initialize before entering in the critical section, as this does
+	 * allocations.
+	 */
+	if (forkNum == INIT_FORKNUM)
+		state = GenericXLogStart(rel);
+
+	START_CRIT_SECTION();
+
+	MarkBufferDirty(buf);
+
+	offnum = PageAddItem(page, tuple->t_data, tuple->t_len,
+						 InvalidOffsetNumber, false, false);
+	if (offnum != FirstOffsetNumber)
+		elog(ERROR, "failed to add sequence tuple to page");
+
+	/*
+	 * Init forks have to be logged.  These go through generic WAL records
+	 * for simplicity's sake to save from the need of a custom RMGR.
+	 */
+	if (forkNum == INIT_FORKNUM)
+	{
+		(void) GenericXLogRegisterBuffer(state, buf, GENERIC_XLOG_FULL_IMAGE);
+		GenericXLogFinish(state);
+	}
+
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buf);
+}
+
+/*
+ * Initialize snowflake relation.
+ *
+ * This needs to handle both the initial and main forks.
+ */
+static void
+fill_snowflake(Relation rel, HeapTuple tuple)
+{
+	SMgrRelation srel;
+
+	Assert(rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED);
+
+	fill_snowflake_fork(rel, tuple, MAIN_FORKNUM);
+
+	/* init fork */
+	srel = smgropen(rel->rd_locator, INVALID_PROC_NUMBER);
+	smgrcreate(srel, INIT_FORKNUM, false);
+	log_smgrcreate(&rel->rd_locator, INIT_FORKNUM);
+	fill_snowflake_fork(rel, tuple, INIT_FORKNUM);
+	FlushRelationBuffers(rel);
+	smgrclose(srel);
+}
+
+/*
+ * Read the current state of a snowflake sequence
+ *
+ * Given an opened sequence relation, lock the page buffer and find the tuple.
+ *
+ * *buf receives the reference to the pinned-and-ex-locked buffer.
+ * *seqdatatuple receives the reference to the sequence tuple proper.
+ *
+ * Returns value points to the data payload of the tuple.
+ */
+static Form_snowflake_data
+read_snowflake(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
+{
+	snowflake_magic *sm;
+	Form_snowflake_data	seq;
+
+	/* Retrieve data from the sequence page */
+	SEQUENCE_PAGE_READ(Form_snowflake_data, snowflake_magic, SNOWFLAKE_MAGIC);
+	return seq;
+}
+
+
+/* ------------------------------------------------------------------------
+ * Callbacks for the snowflake sequence access method.
+ * ------------------------------------------------------------------------
+ */
+
+/*
+ * Return the table access method used by this sequence.
+ *
+ * This is just an on-memory sequence, so anything is fine.
+ */
+static const char *
+snowflake_sequenceam_get_table_am(void)
+{
+	return "heap";
+}
+
+/*
+ * snowflake_sequenceam_init
+ *
+ * Initialize relation of a snowflake sequence.  This stores the sequence
+ * counter in an unlogged relation as timestamps ensure value unicity.
+ */
+static void
+snowflake_sequenceam_init(Relation rel, int64 last_value, bool is_called)
+{
+	Datum	values[SNOWFLAKE_COLS];
+	bool	nulls[SNOWFLAKE_COLS];
+	int16	counter;
+	TupleDesc	tupdesc;
+	HeapTuple	tuple;
+	List	   *elts = NIL;
+	ListCell   *lc;
+	ColumnDef  *coldef = NULL;
+	AlterTableCmd *atcmd;
+	List		  *atcmds = NIL;
+
+	/* Adjust last_value, depending on the defaults given */
+	counter = ((int16) last_value) & SNOWFLAKE_COUNTER_MASK;
+
+	/*
+	 * Create unlogged relation with its attributes.
+	 */
+	coldef = makeColumnDef("count", INT2OID, -1, InvalidOid);
+	coldef->is_not_null = true;
+	elts = lappend(elts, coldef);
+	coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid);
+	coldef->is_not_null = true;
+	elts = lappend(elts, coldef);
+
+	foreach(lc, elts)
+	{
+		atcmd = makeNode(AlterTableCmd);
+		atcmd->subtype = AT_AddColumnToSequence;
+		atcmd->def = (Node *) lfirst(lc);
+		atcmds = lappend(atcmds, atcmd);
+	}
+
+	/*
+	 * No recursion needed.  Note that EventTriggerAlterTableStart() should
+	 * have been called.
+	 */
+	AlterTableInternal(RelationGetRelid(rel), atcmds, false);
+	CommandCounterIncrement();
+
+	/*
+	 * Switch the relation to be unlogged.  This forces a rewrite, but
+	 * the relation is empty so that's OK.
+	 */
+	RelationSetNewRelfilenumber(rel, RELPERSISTENCE_UNLOGGED);
+
+	/* And insert its first tuple */
+	values[0] = Int16GetDatum(counter);
+	nulls[0] = false;
+	values[1] = BoolGetDatum(is_called);
+	nulls[1] = false;
+
+	tupdesc = RelationGetDescr(rel);
+	tuple = heap_form_tuple(tupdesc, values, nulls);
+	fill_snowflake(rel, tuple);
+}
+
+/*
+ * snowflake_sequenceam_nextval
+ *
+ * Return the next value for a snowflake sequence.
+ */
+static int64
+snowflake_sequenceam_nextval(Relation rel, int64 incby, int64 maxv,
+							 int64 minv, int64 cache, bool cycle,
+							 int64 *last)
+{
+	Buffer		buf;
+	Form_snowflake_data seq;
+	HeapTupleData seqdatatuple;
+	int64	result = 0;
+	snowflake_id	id = {0};
+	struct timeval tp;
+
+	/* lock page buffer and read tuple */
+	seq = read_snowflake(rel, &buf, &seqdatatuple);
+
+	/*
+	 * The logic here is quite simple, increment the counter until its
+	 * threshold is reached and get back to the start.  If the threshold
+	 * is reached, wait 1ms to ensure a unique timestamp.  There is no
+	 * need to do a retry as the buffer is already locked.
+	 */
+	id.count = seq->count;
+	id.count++;
+
+	if (id.count > (PG_INT16_MAX & SNOWFLAKE_COUNTER_MASK))
+	{
+		/*
+		 * Threshold reached, so wait a bit for force clock to a new
+		 * timestamp.
+		 */
+		id.count = 1;
+		pg_usleep(1000L);	/* 1ms */
+	}
+
+	/* Compute timestamp, with buffer locked */
+	gettimeofday(&tp, NULL);
+	id.time_ms = (uint64) tp.tv_sec * 1000 +
+		tp.tv_usec / 1000;
+
+	/* Machine ID */
+	id.machine = snowflake_machine_id;
+
+	/* ready to change the on-disk (or really, in-buffer) tuple */
+	START_CRIT_SECTION();
+	seq->count = id.count;
+	seq->is_called = true;
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buf);
+
+	/* Store the last value computed for lastval() */
+	snowflake_id_to_int64(id, result);
+	*last = result;
+	return result;
+}
+
+/*
+ * snowflake_sequenceam_setval
+ *
+ * Set the sequence value, manipulating only the sequence counter.
+ */
+static void
+snowflake_sequenceam_setval(Relation rel, int64 next, bool iscalled)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_snowflake_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_snowflake(rel, &buf, &seqdatatuple);
+
+	/* Change the in-buffer tuple */
+	START_CRIT_SECTION();
+	seq->count = (next & SNOWFLAKE_COUNTER_MASK);
+	seq->is_called = iscalled;
+	MarkBufferDirty(buf);
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buf);
+}
+
+/*
+ * snowflake_sequenceam_get_state
+ *
+ * Return the last sequence counter value.
+ */
+static void
+snowflake_sequenceam_get_state(Relation rel,
+							   int64 *last_value,
+							   bool *is_called,
+							   XLogRecPtr *page_lsn)
+{
+	Buffer		buf;
+	Page		page;
+	HeapTupleData seqdatatuple;
+	Form_snowflake_data seq;
+
+	seq = read_snowflake(rel, &buf, &seqdatatuple);
+	page = BufferGetPage(buf);
+
+	*last_value = seq->count;
+	*is_called = seq->is_called;
+	*page_lsn = PageGetLSN(page);
+
+	UnlockReleaseBuffer(buf);
+}
+
+/*
+ * snowflake_sequenceam_reset
+ *
+ * Reset the sequence, coming down to resetting its counter.
+ */
+static void
+snowflake_sequenceam_reset(Relation rel, int64 startv, bool is_called,
+						   bool reset_state)
+{
+	HeapTupleData seqdatatuple;
+	HeapTuple	tuple;
+	Form_snowflake_data seq;
+	Buffer		buf;
+
+	/* lock buffer page and read tuple */
+	(void) read_snowflake(rel, &buf, &seqdatatuple);
+
+	/* copy the existing tuple */
+	tuple = heap_copytuple(&seqdatatuple);
+
+	/* Now we're done with the old page */
+	UnlockReleaseBuffer(buf);
+
+	/*
+	 * Modify the copied tuple to execute the restart (compare the RESTART
+	 * action in AlterSequence)
+	 */
+	seq = (Form_snowflake_data) GETSTRUCT(tuple);
+	seq->count = (startv & SNOWFLAKE_COUNTER_MASK);
+	seq->is_called = is_called;
+
+	/* create new storage */
+	RelationSetNewRelfilenumber(rel, rel->rd_rel->relpersistence);
+
+	/* insert the modified tuple into the page */
+	fill_snowflake(rel, tuple);
+}
+
+/*
+ * snowflake_sequenceam_change_persistence
+ *
+ * There is nothing to do here, the underneath relation has to remain
+ * unlogged and is set as such when creating the sequence.
+ */
+static void
+snowflake_sequenceam_change_persistence(Relation rel, char newrelpersistence)
+{
+	/* Nothing to do here */
+}
+
+/* ------------------------------------------------------------------------
+ * Definition of the snowflake sequence access method.
+ * ------------------------------------------------------------------------
+ */
+
+static const SequenceAmRoutine snowflake_sequenceam_methods = {
+	.type = T_SequenceAmRoutine,
+	.get_table_am = snowflake_sequenceam_get_table_am,
+	.init = snowflake_sequenceam_init,
+	.nextval = snowflake_sequenceam_nextval,
+	.setval = snowflake_sequenceam_setval,
+	.get_state = snowflake_sequenceam_get_state,
+	.reset = snowflake_sequenceam_reset,
+	.change_persistence = snowflake_sequenceam_change_persistence
+};
+
+Datum
+snowflake_sequenceam_handler(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_POINTER(&snowflake_sequenceam_methods);
+}
+
+/* Utility functions */
+
+/*
+ * snowflake_get
+ *
+ * Return a tuple worth of snowflake ID data, in a readable shape.
+ */
+PG_FUNCTION_INFO_V1(snowflake_get);
+Datum
+snowflake_get(PG_FUNCTION_ARGS)
+{
+#define	SNOWFLAKE_GET_COLS	3
+	int64	raw = PG_GETARG_INT64(0);
+	Datum  *values;
+	bool   *nulls;
+	TupleDesc		tupdesc;
+	snowflake_id	id;
+
+	/* determine result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	int64_to_snowflake_id(raw, id);
+
+	nulls = palloc0(sizeof(bool) * tupdesc->natts);
+	values = palloc0(sizeof(Datum) * tupdesc->natts);
+
+	values[0] = Int64GetDatum(id.time_ms);
+	values[1] = Int32GetDatum(id.machine);
+	values[2] = Int32GetDatum(id.count);
+
+	/* Returns the record as Datum */
+	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
+}
+
+/*
+ * Entry point when loading extension.
+ */
+void
+_PG_init(void)
+{
+	DefineCustomIntVariable("snowflake.machine_id",
+							"Machine ID to use with snowflake sequence.",
+							"Default value is 1.",
+							&snowflake_machine_id,
+							1, 0, 1023, /* 10 bits as max */
+							PGC_SUSET,
+							0, NULL, NULL, NULL);
+}
diff --git a/contrib/snowflake/snowflake.control b/contrib/snowflake/snowflake.control
new file mode 100644
index 000000000000..7b8c6089c25f
--- /dev/null
+++ b/contrib/snowflake/snowflake.control
@@ -0,0 +1,5 @@
+# snowflake extension
+comment = 'snowflake - sequence access method'
+default_version = '1.0'
+module_pathname = '$libdir/snowflake'
+relocatable = true
diff --git a/contrib/snowflake/sql/snowflake.sql b/contrib/snowflake/sql/snowflake.sql
new file mode 100644
index 000000000000..395d166ba4bc
--- /dev/null
+++ b/contrib/snowflake/sql/snowflake.sql
@@ -0,0 +1,29 @@
+CREATE EXTENSION snowflake;
+
+CREATE SEQUENCE snowflake_seq USING snowflake;
+
+SET snowflake.machine_id = 2000; -- error
+SET snowflake.machine_id = 4; -- ok
+SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq'));
+SELECT machine, counter FROM snowflake_get(lastval());
+SELECT machine, counter FROM snowflake_get(nextval('snowflake_seq'));
+SELECT machine, counter FROM snowflake_get(currval('snowflake_seq'));
+
+-- Sequence relation exists, is unlogged and remains unlogged.
+SELECT * FROM snowflake_seq;
+ALTER SEQUENCE snowflake_seq SET LOGGED;
+SELECT relpersistence FROM pg_class where relname = 'snowflake_seq';
+
+ALTER SEQUENCE snowflake_seq RESTART;
+SELECT * FROM snowflake_seq;
+
+-- Identity column, where cache affects value.
+SET default_sequence_access_method = 'snowflake';
+CREATE TABLE snowflake_tab (a int GENERATED ALWAYS AS IDENTITY, b int);
+INSERT INTO snowflake_tab VALUES (DEFAULT, generate_series(1, 10));
+SELECT data.machine, data.counter
+  FROM snowflake_tab, LATERAL snowflake_get(a) AS data;
+DROP TABLE snowflake_tab;
+
+DROP SEQUENCE snowflake_seq;
+DROP EXTENSION snowflake;
-- 
2.51.0



  [application/pgp-signature] signature.asc (833B, 9-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 3+ messages in thread

* Re: Sequence Access Methods, round two
  2026-01-07 05:32 Re: Sequence Access Methods, round two Michael Paquier <[email protected]>
@ 2026-05-18 18:42 ` Andrei Lepikhov <[email protected]>
  2026-05-19 13:11   ` Re: Sequence Access Methods, round two Andrei Lepikhov <[email protected]>
  0 siblings, 1 reply; 3+ messages in thread

From: Andrei Lepikhov @ 2026-05-18 18:42 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Postgres hackers <[email protected]>; Xuneng Zhou <[email protected]>; Chao Li <[email protected]>; Peter Eisentraut <[email protected]>; Kirill Reshke <[email protected]>; Peter Smith <[email protected]>

On 18/05/2026 00:43, Michael Paquier wrote:
> On Sun, May 17, 2026 at 08:03:15AM +0200, Andrei Lepikhov wrote:
>> Right now, awaiting this feature, I use a nextval hook. But it is just to
>> minimise the number of core lines that need to be changed. Neither hook nor
>> callback is a good idea here - sequence source might be only one for a specific
>> table; \d should show an unequivocal definition of a table.
>> Also, the AM machinery makes the dump/restore use cases clear. Logical
>> replication plugins also benefit from it: pgactive, pglogical, and spock all
>> include Auto-DDL solutions that simplify the management of sequence generation
>> methods across instances.
> 
> There was zero feedback from other core developers, so it's really
> hard to weigh about its acceptance.  My guess is that nobody really
> cares about this thread, which is just the way it is on -hackers for
> some things.  FWIW, I still like what I've done in this patch and this
> design.

Ok. So let me just leave the idea of avoiding unnecessary cache lookups here.

-- 
regards, Andrei Lepikhov,
pgEdge
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2a8e64c7279..1c219ce319d 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1872,12 +1872,24 @@ RelationInitSequenceAccessMethod(Relation relation)
 	Oid			tableam_handler;
 
 	Assert(RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind));
+	Assert(relation->rd_rel->relam != InvalidOid);
 
 	/*
-	 * Look up the sequence access method, save the OID of its handler
-	 * function.
+	 * Fast path for the built-in "seqlocal" AM: avoid two syscache lookups
+	 * and a name-based pg_am scan on every cold-cache open of a sequence
+	 * using the default access method.
+	 *
+	 * This mirrors the catalog-relation fast path in
+	 * RelationInitTableAccessMethod() above.
 	 */
-	Assert(relation->rd_rel->relam != InvalidOid);
+	if (relation->rd_rel->relam == LOCAL_SEQUENCE_AM_OID)
+	{
+		relation->rd_amhandler = F_SEQ_LOCAL_SEQUENCEAM_HANDLER;
+		relation->rd_sequenceam = GetSequenceAmRoutine(relation->rd_amhandler);
+		relation->rd_tableam = GetTableAmRoutine(F_HEAP_TABLEAM_HANDLER);
+		return;
+	}
+
 	relation->rd_amhandler = GetSequenceAmRoutineId(relation->rd_rel->relam);
 
 	/*


Attachments:

  [text/plain] wire_local_seq.diff (1.2K, 2-wire_local_seq.diff)
  download | inline diff:
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2a8e64c7279..1c219ce319d 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1872,12 +1872,24 @@ RelationInitSequenceAccessMethod(Relation relation)
 	Oid			tableam_handler;
 
 	Assert(RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind));
+	Assert(relation->rd_rel->relam != InvalidOid);
 
 	/*
-	 * Look up the sequence access method, save the OID of its handler
-	 * function.
+	 * Fast path for the built-in "seqlocal" AM: avoid two syscache lookups
+	 * and a name-based pg_am scan on every cold-cache open of a sequence
+	 * using the default access method.
+	 *
+	 * This mirrors the catalog-relation fast path in
+	 * RelationInitTableAccessMethod() above.
 	 */
-	Assert(relation->rd_rel->relam != InvalidOid);
+	if (relation->rd_rel->relam == LOCAL_SEQUENCE_AM_OID)
+	{
+		relation->rd_amhandler = F_SEQ_LOCAL_SEQUENCEAM_HANDLER;
+		relation->rd_sequenceam = GetSequenceAmRoutine(relation->rd_amhandler);
+		relation->rd_tableam = GetTableAmRoutine(F_HEAP_TABLEAM_HANDLER);
+		return;
+	}
+
 	relation->rd_amhandler = GetSequenceAmRoutineId(relation->rd_rel->relam);
 
 	/*


^ permalink  raw  reply  [nested|flat] 3+ messages in thread

* Re: Sequence Access Methods, round two
  2026-01-07 05:32 Re: Sequence Access Methods, round two Michael Paquier <[email protected]>
  2026-05-18 18:42 ` Re: Sequence Access Methods, round two Andrei Lepikhov <[email protected]>
@ 2026-05-19 13:11   ` Andrei Lepikhov <[email protected]>
  0 siblings, 0 replies; 3+ messages in thread

From: Andrei Lepikhov @ 2026-05-19 13:11 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Postgres hackers <[email protected]>; Xuneng Zhou <[email protected]>; Chao Li <[email protected]>; Peter Eisentraut <[email protected]>; Kirill Reshke <[email protected]>; Peter Smith <[email protected]>

On 18/05/2026 20:42, Andrei Lepikhov wrote:
> On 18/05/2026 00:43, Michael Paquier wrote:
> Ok. So let me just leave the idea of avoiding unnecessary cache lookups here.
Here is my benchmark results.

Test script:

DROP SEQUENCE IF EXISTS abc;
CREATE UNLOGGED SEQUENCE abc USING seqlocal;
-- Warm up
SELECT count(nextval('abc')) FROM generate_series(1, 1E6) \watch i=0 c=100
\timing on
\o result.txt
-- Benchmark
SELECT count(nextval('abc')) FROM generate_series(1, 1E6) \watch i=0 c=100

To identify thermal impact I averaged and compared results per 20 iterations in
a pack:

| Window      | Baseline | v29          |
| ----------- | -------- | ------------ |
| iter 1-20   | 236.92   | 235.28       |
| iter 21-40  | 235.77   | 236.17       |
| iter 41-60  | 238.53   | 234.64       |
| iter 61-80  | 236.62   | 234.35       |
| iter 81-100 | 237.13   | 235.24       |

This test doesn't include insertion machinery itself, so no cold cache measures
or sequence lock contention.

pgbench results on the UNLOGGED table insertion shows the following:

| Test                           | Baseline   | v29          | Δ      |
| INSERT pgbench (mean tps)      | 24,997     | 25,195       | +0.79% |

My oldish Intel-based MacBook is probably not sensitive enough to detect the
overhead. So, you can recheck the result using scripts [1] to restore the exact
test.

[1] https://github.com/danolivo/conf/tree/main/2026e-SeqAM

-- 
regards, Andrei Lepikhov,
pgEdge






^ permalink  raw  reply  [nested|flat] 3+ messages in thread


end of thread, other threads:[~2026-05-19 13:11 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-01-07 05:32 Re: Sequence Access Methods, round two Michael Paquier <[email protected]>
2026-05-18 18:42 ` Andrei Lepikhov <[email protected]>
2026-05-19 13:11   ` Andrei Lepikhov <[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