Sequence Access Methods, round two

Started by Michael Paquierabout 2 years ago66 messages
#1Michael Paquier
michael@paquier.xyz
9 attachment(s)

Hi all,

Back in 2016, a patch set has been proposed to add support for
sequence access methods:
/messages/by-id/CA+U5nMLV3ccdzbqCvcedd-HfrE4dUmoFmTBPL_uJ9YjsQbR7iQ@mail.gmail.com

This included quite a few concepts, somewhat adapted to the point
where this feature was proposed:
- Addition of USING clause for CREATE/ALTER SEQUENCE.
- Addition of WITH clause for sequences, with custom reloptions.
- All sequences rely on heap
- The user-case focused on was the possibility to have cluster-wide
sequences, with sequence storage always linked to heap.
- Dump/restore logic depended on that, with a set of get/set functions
to be able to retrieve and force a set of properties to sequences.

A bunch of the implementation and design choices back then come down
to the fact that *all* the sequence properties were located in a
single heap file, including start, restart, cycle, increment, etc.
Postgres 10 has split this data with the introduction of the catalog
pg_sequence, that has moved all the sequence properties within it.
As a result, the sequence "heap" metadata got reduced to its
last_value, is_called and log_cnt (to count if a metadata tuple should
be WAL-logged). Honestly, I think we can do simpler than the original
proposal, while satisfying more cases than what the original thread
wanted to address. One thing is that a sequence AM may want storage,
but it should be able to plug in to a table AM of its choice.

Please find attached a patch set that aims at implementing sequence
access methods, with callbacks following a model close to table and
index AMs, with a few cases in mind:
- Global sequences (including range-allocation, local caching).
- Local custom computations (a-la-snowflake).

The patch set has been reduced to what I consider the minimum
acceptable for an implementation, with some properties like:
- Implementation of USING in CREATE SEQUENCE only, no need for WITH
and reloptions (could be added later).
- Implementation of dump/restore, with a GUC to force a default
sequence AM, and a way to dump/restore without a sequence AM set (like
table AMs, this relies on SET and a --no-sequence-access-method).
- Sequence AMs can use a custom table AM to store its meta-data, which
could be heap, or something else. A sequence AM is free to choose if
it should store data or not, and can plug into a custom RMGR to log
data.
- Ensure compatibility with the existing in-core method, called
"local" in this patch set. This uses a heap table, and a local
sequence AM registers the same pg_class entry as past releases.
Perhaps this should have a less generic name, like "seqlocal",
"sequence_local", but I have a bad tracking history when it comes to
name things. I've just inherited the name from the patch of 2016.
- pg_sequence is used to provide hints (or advices) to the sequence
AM about the way to compute values. A nice side effect of that is
that cross-property check for sequences are the same for all sequence
AMs. This gives a clean split between pg_sequence and the metadata
managed by sequence AMs.

On HEAD, sequence.c holds three different concepts, and decided that
this stuff should actually split them for table AMs:
1) pg_sequence and general sequence properties.
2) Local sequence cache, for lastval(), depending on the last sequence
value fetched.
3) In-core sequence metadata, used to grab or set values for all
the flavors of setval(), nextval(), etc.

With a focus on compatibility, the key design choices here are that 1)
and 2) have the same rules shared across all AMs, and 3) is something
that sequence AMs are free to play with as they want. Using this
concept, the contents of 3) in sequence.c are now local into the
"local" sequence AM:
- RMGR for sequences, as of xl_seq_rec and RM_SEQ_ID (renamed to use
"local" as well).
- FormData_pg_sequence_data for the local sequence metadata, including
its attributes, SEQ_COL_*, the internal routines managing rewrites of
its heap, etc.
- In sequence.c, log_cnt is not a counter, just a way to decide if a
sequence metadata should be reset or not (note that init_params() only
resets it to 0 if sequence properties are changed).
As a result, 30% of sequence.c is trimmed of its in-core AM concepts,
all moved to local.c.

While working on this patch, I've finished by keeping a focus on
dump/restore permeability and focus on being able to use nextval(),
setval(), lastval() and even pg_sequence_last_value() across all AMs
so as it makes integration with things like SERIAL or GENERATED
columns natural. Hence, the callbacks are shaped so as these
functions are transparent across all sequence AMs. See sequenceam.h
for the details about them, and local.c for the "local" sequence AM.

The attached patch set covers all the ground I wanted to cover with
this stuff, including dump/restore, tests, docs, compatibility, etc,
etc. I've done a first split of things to make the review more
edible, as there are a few independent pieces I've bumped into while
implementing the callbacks.

Here are some independent refactoring pieces:
- 0001 is something to make dump/restore transparent across all
sequence AMs. Currently, pg_dump queries sequence heap tables, but a
sequence AM may not have any storage locally, or could grab its values
from elsewhere. pg_sequence_last_value(), a non-documented function
used for pg_sequence, is extended so as it returns a row made of
(last_value, is_called), so as it can be used for dump data, across
all AMs.
- 0002 introduces sequence_open() and sequence_close(). Like tables
and indexes, this is coupled with a relkind check, and used as the
sole way to open sequences in sequence.c.
- 0003 groups the sequence cache updates of sequence.c closer to each
other. This stuff was hidden in the middle of unrelated computations.
- 0004 removes all traces of FormData_pg_sequence_data from
init_params(), which is used to guess the start value and is_called
for a sequence depending on its properties in the catalog pg_sequence.
- 0005 is an interesting one. I've noticed that I wanted to attach
custom attributes to the pg_class entry of a sequence, or just not
store any attributes at *all* within it. One approach I have
considered first is to list for the attributes to send to
DefineRelation() within each AM, but this requires an early lookup at
the sequence AM routines, which was gross. Instead, I've chosen the
method similar to views, where attributes are added after the relation
is defined, using AlterTableInternal(). This simplifies the set of
callbacks so as initialization is in charge of creating the sequence
attributes (if necessary) and add the first batch of metadata tuple
for a sequence (again, if necessary). The changes that reflect to
event triggers and the commands collected is also something I've
wanted, as it becomes possible to track what gets internally created
for a sequence depending on its AM (see test_ddl_deparse).

Then comes the core of the changes, with a split depending on code
paths:
- 0006 includes the backend changes, that caches a set of callback
routines for each sequence Relation, with an optional rd_tableam.
Callbacks are documented in sequenceam.h. Perhaps the sequence RMGR
renames should be split into a patch of its own, or just let as-is as
as it could be shared across more than one AM, but I did not see a
huge argument one way or another. The diffs are not that bad
considering that the original patch at +1200 lines for src/backend/,
with less documentation for the internal callbacks:
45 files changed, 1414 insertions(+), 718 deletions(-)
- 0007 adds some documentation.
- 0008 adds support for dump/restore, where I have also incorporated
tests and docs. The implementation finishes by being really
straight-forward, relying on a new option switch to control if
SET queries for sequence AMs should be dumped and/or restored,
depending ona GUC called default_sequence_access_method.
- 0009 is a short example of sequence AM, which is a kind of in-memory
sequence reset each time a new connection is made, without any
physical storage. I am not clear yet if that's useful as local.c can
be used as a point of reference, but I was planning to include that in
one of my own repos on github like my blackhole_am.

I am adding that to the next CF. Thoughts and comments are welcome.
--
Michael

Attachments:

v1-0001-Switch-pg_sequence_last_value-to-report-a-tuple-a.patchtext/x-diff; charset=us-asciiDownload
From 50c77901ee08bf7cf8d9cc80be2df8e78bfcc8bb Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 09:37:24 +0900
Subject: [PATCH v1 1/9] Switch pg_sequence_last_value() to report a tuple and
 use it in pg_dump

This commit switches pg_sequence_last_value() to report a tuple made of
(last_value,is_called) that can be directly be used for the arguments of
setval() in a sequence.

Going forward with PostgreSQL 17, pg_dump and pg_sequences make use of
it instead of scanning the heap table assumed to always exist for a
sequence.

Note: this requires a catversion bump.
---
 src/include/catalog/pg_proc.dat      |  6 ++++--
 src/backend/catalog/system_views.sql |  6 +++++-
 src/backend/commands/sequence.c      | 19 +++++++++++++------
 src/bin/pg_dump/pg_dump.c            | 16 +++++++++++++---
 src/test/regress/expected/rules.out  |  7 ++++++-
 5 files changed, 41 insertions(+), 13 deletions(-)

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fb58dee3bc..5999952da3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3325,9 +3325,11 @@
   proargmodes => '{i,o,o,o,o,o,o,o}',
   proargnames => '{sequence_oid,start_value,minimum_value,maximum_value,increment,cycle_option,cache_size,data_type}',
   prosrc => 'pg_sequence_parameters' },
-{ oid => '4032', descr => 'sequence last value',
+{ oid => '4032', descr => 'sequence last value data',
   proname => 'pg_sequence_last_value', provolatile => 'v', proparallel => 'u',
-  prorettype => 'int8', proargtypes => 'regclass',
+  prorettype => 'record', proargtypes => 'regclass',
+  proallargtypes => '{regclass,bool,int8}', proargmodes => '{i,o,o}',
+  proargnames => '{seqname,is_called,last_value}',
   prosrc => 'pg_sequence_last_value' },
 
 { oid => '275', descr => 'return the next oid for a system table',
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 11d18ed9dd..009940dd80 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -178,7 +178,11 @@ CREATE VIEW pg_sequences AS
         S.seqcache AS cache_size,
         CASE
             WHEN has_sequence_privilege(C.oid, 'SELECT,USAGE'::text)
-                THEN pg_sequence_last_value(C.oid)
+                THEN (SELECT
+                          CASE WHEN sl.is_called
+                              THEN sl.last_value ELSE NULL
+                          END
+                      FROM pg_sequence_last_value(C.oid) sl)
             ELSE NULL
         END AS last_value
     FROM pg_sequence S JOIN pg_class C ON (C.oid = S.seqrelid)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index da2ace79cc..9c94255f24 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1779,14 +1779,22 @@ pg_sequence_parameters(PG_FUNCTION_ARGS)
 Datum
 pg_sequence_last_value(PG_FUNCTION_ARGS)
 {
+#define PG_SEQUENCE_LAST_VALUE_COLS		2
 	Oid			relid = PG_GETARG_OID(0);
+	Datum		values[PG_SEQUENCE_LAST_VALUE_COLS] = {0};
+	bool		nulls[PG_SEQUENCE_LAST_VALUE_COLS] = {0};
 	SeqTable	elm;
 	Relation	seqrel;
+	TupleDesc	tupdesc;
 	Buffer		buf;
 	HeapTupleData seqtuple;
 	Form_pg_sequence_data seq;
 	bool		is_called;
-	int64		result;
+	int64		last_value;
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	/* open and lock sequence */
 	init_sequence(relid, &elm, &seqrel);
@@ -1800,15 +1808,14 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	seq = read_seq_tuple(seqrel, &buf, &seqtuple);
 
 	is_called = seq->is_called;
-	result = seq->last_value;
+	last_value = seq->last_value;
 
 	UnlockReleaseBuffer(buf);
 	relation_close(seqrel, NoLock);
 
-	if (is_called)
-		PG_RETURN_INT64(result);
-	else
-		PG_RETURN_NULL();
+	values[0] = BoolGetDatum(is_called);
+	values[1] = Int64GetDatum(last_value);
+	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
 }
 
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8c0b5486b9..c7612c3793 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -17476,9 +17476,19 @@ dumpSequenceData(Archive *fout, const TableDataInfo *tdinfo)
 	bool		called;
 	PQExpBuffer query = createPQExpBuffer();
 
-	appendPQExpBuffer(query,
-					  "SELECT last_value, is_called FROM %s",
-					  fmtQualifiedDumpable(tbinfo));
+	/*
+	 * In versions 17 and up, pg_sequence_last_value() has been switched to
+	 * return a tuple with last_value and is_called.
+	 */
+	if (fout->remoteVersion >= 170000)
+		appendPQExpBuffer(query,
+						  "SELECT last_value, is_called "
+						  "FROM pg_sequence_last_value('%s')",
+						  fmtQualifiedDumpable(tbinfo));
+	else
+		appendPQExpBuffer(query,
+						  "SELECT last_value, is_called FROM %s",
+						  fmtQualifiedDumpable(tbinfo));
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 05070393b9..105e8f5eb4 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1696,7 +1696,12 @@ pg_sequences| SELECT n.nspname AS schemaname,
     s.seqcycle AS cycle,
     s.seqcache AS cache_size,
         CASE
-            WHEN has_sequence_privilege(c.oid, 'SELECT,USAGE'::text) THEN pg_sequence_last_value((c.oid)::regclass)
+            WHEN has_sequence_privilege(c.oid, 'SELECT,USAGE'::text) THEN ( SELECT
+                    CASE
+                        WHEN sl.is_called THEN sl.last_value
+                        ELSE NULL::bigint
+                    END AS "case"
+               FROM pg_sequence_last_value((c.oid)::regclass) sl(is_called, last_value))
             ELSE NULL::bigint
         END AS last_value
    FROM ((pg_sequence s
-- 
2.43.0

v1-0002-Introduce-sequence_-access-functions.patchtext/x-diff; charset=us-asciiDownload
From a6574906da8aa0ef7ce2f6df718a3d19a5cd89ed Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 10:02:07 +0900
Subject: [PATCH v1 2/9] Introduce sequence_*() access functions

Similarly to tables and indexes, these functions are only able to open
relations with a sequence relkind, which is useful to make a distinction
with the other types that can have AMs.
---
 src/include/access/sequence.h           | 23 ++++++++
 src/backend/access/Makefile             |  2 +-
 src/backend/access/meson.build          |  1 +
 src/backend/access/sequence/Makefile    | 17 ++++++
 src/backend/access/sequence/meson.build |  5 ++
 src/backend/access/sequence/sequence.c  | 78 +++++++++++++++++++++++++
 src/backend/commands/sequence.c         | 31 +++++-----
 src/test/regress/expected/sequence.out  |  3 +-
 8 files changed, 140 insertions(+), 20 deletions(-)
 create mode 100644 src/include/access/sequence.h
 create mode 100644 src/backend/access/sequence/Makefile
 create mode 100644 src/backend/access/sequence/meson.build
 create mode 100644 src/backend/access/sequence/sequence.c

diff --git a/src/include/access/sequence.h b/src/include/access/sequence.h
new file mode 100644
index 0000000000..5d2a3d8a71
--- /dev/null
+++ b/src/include/access/sequence.h
@@ -0,0 +1,23 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequence.h
+ *	  Generic routines for sequence related code.
+ *
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/sequence/sequence.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ACCESS_SEQUENCE_H
+#define ACCESS_SEQUENCE_H
+
+#include "storage/lockdefs.h"
+#include "utils/relcache.h"
+
+extern Relation sequence_open(Oid relationId, LOCKMODE lockmode);
+extern void sequence_close(Relation relation, LOCKMODE lockmode);
+
+#endif							/* ACCESS_SEQUENCE_H */
diff --git a/src/backend/access/Makefile b/src/backend/access/Makefile
index 0880e0a8bb..1932d11d15 100644
--- a/src/backend/access/Makefile
+++ b/src/backend/access/Makefile
@@ -9,6 +9,6 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 SUBDIRS	    = brin common gin gist hash heap index nbtree rmgrdesc spgist \
-			  table tablesample transam
+			  sequence table tablesample transam
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/meson.build b/src/backend/access/meson.build
index aa99c4d162..ee08dcec9b 100644
--- a/src/backend/access/meson.build
+++ b/src/backend/access/meson.build
@@ -9,6 +9,7 @@ subdir('heap')
 subdir('index')
 subdir('nbtree')
 subdir('rmgrdesc')
+subdir('sequence')
 subdir('spgist')
 subdir('table')
 subdir('tablesample')
diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile
new file mode 100644
index 0000000000..9f9d31f542
--- /dev/null
+++ b/src/backend/access/sequence/Makefile
@@ -0,0 +1,17 @@
+#-------------------------------------------------------------------------
+#
+# Makefile
+#    Makefile for access/sequence
+#
+# IDENTIFICATION
+#    src/backend/access/sequence/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/access/sequence
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = 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
new file mode 100644
index 0000000000..1840a913bc
--- /dev/null
+++ b/src/backend/access/sequence/meson.build
@@ -0,0 +1,5 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+backend_sources += files(
+  'sequence.c',
+)
diff --git a/src/backend/access/sequence/sequence.c b/src/backend/access/sequence/sequence.c
new file mode 100644
index 0000000000..a5b9fccb50
--- /dev/null
+++ b/src/backend/access/sequence/sequence.c
@@ -0,0 +1,78 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequence.c
+ *	  Generic routines for sequence related code.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/sequence/sequence.c
+ *
+ *
+ * NOTES
+ *	  This file contains sequence_ routines that implement access to sequences
+ *	  (in contrast to other relation types like indexes).
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/relation.h"
+#include "access/sequence.h"
+#include "storage/lmgr.h"
+
+static inline void validate_relation_kind(Relation r);
+
+/* ----------------
+ *		sequence_open - open a sequence relation by relation OID
+ *
+ *		This is essentially relation_open plus check that the relation
+ *		is a sequence.
+ * ----------------
+ */
+Relation
+sequence_open(Oid relationId, LOCKMODE lockmode)
+{
+	Relation	r;
+
+	r = relation_open(relationId, lockmode);
+
+	validate_relation_kind(r);
+
+	return r;
+}
+
+/* ----------------
+ *		sequence_close - close a sequence
+ *
+ *		If lockmode is not "NoLock", we then release the specified lock.
+ *
+ *		Note that it is often sensible to hold a lock beyond relation_close;
+ *		in that case, the lock is released automatically at xact end.
+ *		----------------
+ */
+void
+sequence_close(Relation relation, LOCKMODE lockmode)
+{
+	relation_close(relation, lockmode);
+}
+
+/* ----------------
+ *		validate_relation_kind - check the relation's kind
+ *
+ *		Make sure relkind is from an index
+ * ----------------
+ */
+static inline void
+validate_relation_kind(Relation r)
+{
+	if (r->rd_rel->relkind != RELKIND_SEQUENCE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot open relation \"%s\"",
+						RelationGetRelationName(r)),
+				 errdetail_relkind_not_supported(r->rd_rel->relkind)));
+}
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 9c94255f24..117518d480 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -18,6 +18,7 @@
 #include "access/htup_details.h"
 #include "access/multixact.h"
 #include "access/relation.h"
+#include "access/sequence.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -208,7 +209,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	seqoid = address.objectId;
 	Assert(seqoid != InvalidOid);
 
-	rel = table_open(seqoid, AccessExclusiveLock);
+	rel = sequence_open(seqoid, AccessExclusiveLock);
 	tupDesc = RelationGetDescr(rel);
 
 	/* now initialize the sequence's data */
@@ -219,7 +220,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	if (owned_by)
 		process_owned_by(rel, owned_by, seq->for_identity);
 
-	table_close(rel, NoLock);
+	sequence_close(rel, NoLock);
 
 	/* fill in pg_sequence */
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
@@ -324,7 +325,7 @@ ResetSequence(Oid seq_relid)
 	/* Note that we do not change the currval() state */
 	elm->cached = elm->last;
 
-	relation_close(seq_rel, NoLock);
+	sequence_close(seq_rel, NoLock);
 }
 
 /*
@@ -531,7 +532,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	ObjectAddressSet(address, RelationRelationId, relid);
 
 	table_close(rel, RowExclusiveLock);
-	relation_close(seqrel, NoLock);
+	sequence_close(seqrel, NoLock);
 
 	return address;
 }
@@ -555,7 +556,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence)
 	fill_seq_with_data(seqrel, &seqdatatuple);
 	UnlockReleaseBuffer(buf);
 
-	relation_close(seqrel, NoLock);
+	sequence_close(seqrel, NoLock);
 }
 
 void
@@ -662,7 +663,7 @@ nextval_internal(Oid relid, bool check_permissions)
 		Assert(elm->last_valid);
 		Assert(elm->increment != 0);
 		elm->last += elm->increment;
-		relation_close(seqrel, NoLock);
+		sequence_close(seqrel, NoLock);
 		last_used_seq = elm;
 		return elm->last;
 	}
@@ -849,7 +850,7 @@ nextval_internal(Oid relid, bool check_permissions)
 
 	UnlockReleaseBuffer(buf);
 
-	relation_close(seqrel, NoLock);
+	sequence_close(seqrel, NoLock);
 
 	return result;
 }
@@ -880,7 +881,7 @@ currval_oid(PG_FUNCTION_ARGS)
 
 	result = elm->last;
 
-	relation_close(seqrel, NoLock);
+	sequence_close(seqrel, NoLock);
 
 	PG_RETURN_INT64(result);
 }
@@ -915,7 +916,7 @@ lastval(PG_FUNCTION_ARGS)
 						RelationGetRelationName(seqrel))));
 
 	result = last_used_seq->last;
-	relation_close(seqrel, NoLock);
+	sequence_close(seqrel, NoLock);
 
 	PG_RETURN_INT64(result);
 }
@@ -1030,7 +1031,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 
 	UnlockReleaseBuffer(buf);
 
-	relation_close(seqrel, NoLock);
+	sequence_close(seqrel, NoLock);
 }
 
 /*
@@ -1095,7 +1096,7 @@ lock_and_open_sequence(SeqTable seq)
 	}
 
 	/* We now know we have the lock, and can safely open the rel */
-	return relation_open(seq->relid, NoLock);
+	return sequence_open(seq->relid, NoLock);
 }
 
 /*
@@ -1152,12 +1153,6 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	 */
 	seqrel = lock_and_open_sequence(elm);
 
-	if (seqrel->rd_rel->relkind != RELKIND_SEQUENCE)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a sequence",
-						RelationGetRelationName(seqrel))));
-
 	/*
 	 * If the sequence has been transactionally replaced since we last saw it,
 	 * discard any cached-but-unissued values.  We do not touch the currval()
@@ -1811,7 +1806,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	last_value = seq->last_value;
 
 	UnlockReleaseBuffer(buf);
-	relation_close(seqrel, NoLock);
+	sequence_close(seqrel, NoLock);
 
 	values[0] = BoolGetDatum(is_called);
 	values[1] = Int64GetDatum(last_value);
diff --git a/src/test/regress/expected/sequence.out b/src/test/regress/expected/sequence.out
index 7cb2f7cc02..2b47b7796b 100644
--- a/src/test/regress/expected/sequence.out
+++ b/src/test/regress/expected/sequence.out
@@ -313,7 +313,8 @@ ALTER SEQUENCE IF EXISTS sequence_test2 RESTART WITH 24
   INCREMENT BY 4 MAXVALUE 36 MINVALUE 5 CYCLE;
 NOTICE:  relation "sequence_test2" does not exist, skipping
 ALTER SEQUENCE serialTest1 CYCLE;  -- error, not a sequence
-ERROR:  "serialtest1" is not a sequence
+ERROR:  cannot open relation "serialtest1"
+DETAIL:  This operation is not supported for tables.
 CREATE SEQUENCE sequence_test2 START WITH 32;
 CREATE SEQUENCE sequence_test4 INCREMENT BY -1;
 SELECT nextval('sequence_test2');
-- 
2.43.0

v1-0003-Group-more-closely-local-sequence-cache-updates.patchtext/x-diff; charset=us-asciiDownload
From abecdc53a05b0300abfdf0c3d2356dcbe8a3a766 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 10:08:17 +0900
Subject: [PATCH v1 3/9] Group more closely local sequence cache updates

Previously, some updates of the informations for SeqTable entries was
mixed in the middle of computations.  Grouping them makes the code
easier to follow and split later on.
---
 src/backend/commands/sequence.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 117518d480..ef71efdb82 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -489,10 +489,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 				seqform, newdataform,
 				&need_seq_rewrite, &owned_by);
 
-	/* Clear local cache so that we don't think we have cached numbers */
-	/* Note that we do not change the currval() state */
-	elm->cached = elm->last;
-
 	/* If needed, rewrite the sequence relation itself */
 	if (need_seq_rewrite)
 	{
@@ -520,6 +516,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		fill_seq_with_data(seqrel, newdatatuple);
 	}
 
+	/* Clear local cache so that we don't think we have cached numbers */
+	/* Note that we do not change the currval() state */
+	elm->cached = elm->last;
+
 	/* process OWNED BY if given */
 	if (owned_by)
 		process_owned_by(seqrel, owned_by, stmt->for_identity);
@@ -683,7 +683,6 @@ nextval_internal(Oid relid, bool check_permissions)
 	seq = read_seq_tuple(seqrel, &buf, &seqdatatuple);
 	page = BufferGetPage(buf);
 
-	elm->increment = incby;
 	last = next = result = seq->last_value;
 	fetch = cache;
 	log = seq->log_cnt;
@@ -781,6 +780,7 @@ nextval_internal(Oid relid, bool check_permissions)
 	Assert(log >= 0);
 
 	/* save info in local cache */
+	elm->increment = incby;
 	elm->last = result;			/* last returned number */
 	elm->cached = last;			/* last fetched number */
 	elm->last_valid = true;
-- 
2.43.0

v1-0004-Remove-FormData_pg_sequence_data-from-init_params.patchtext/x-diff; charset=us-asciiDownload
From 898f7c4940f0d37a2e690fad384502433f02f673 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:00:45 +0900
Subject: [PATCH v1 4/9] Remove FormData_pg_sequence_data from
 init_params()/sequence.c

init_params() sets up "last_value" and "is_called" for a sequence, based
on the sequence properties in pg_sequences.  This simplifies the logic
around log_cnt, which is reset to 0 when the metadata of a sequence is
expected to start from afresh when its properties are updated.
---
 src/backend/commands/sequence.c | 81 ++++++++++++++++++++-------------
 1 file changed, 49 insertions(+), 32 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index ef71efdb82..8927f99670 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -106,7 +106,9 @@ static Form_pg_sequence_data read_seq_tuple(Relation rel,
 static void init_params(ParseState *pstate, List *options, bool for_identity,
 						bool isInit,
 						Form_pg_sequence seqform,
-						Form_pg_sequence_data seqdataform,
+						int64 *last_value,
+						bool *reset_state,
+						bool *is_called,
 						bool *need_seq_rewrite,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
@@ -121,7 +123,9 @@ ObjectAddress
 DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 {
 	FormData_pg_sequence seqform;
-	FormData_pg_sequence_data seqdataform;
+	int64		last_value;
+	bool		reset_state;
+	bool		is_called;
 	bool		need_seq_rewrite;
 	List	   *owned_by;
 	CreateStmt *stmt = makeNode(CreateStmt);
@@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* Check and set all option values */
 	init_params(pstate, seq->options, seq->for_identity, true,
-				&seqform, &seqdataform,
+				&seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/*
@@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 		{
 			case SEQ_COL_LASTVAL:
 				coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
-				value[i - 1] = Int64GetDatumFast(seqdataform.last_value);
+				value[i - 1] = Int64GetDatumFast(last_value);
 				break;
 			case SEQ_COL_LOG:
 				coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
@@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	ObjectAddress address;
 	Relation	rel;
 	HeapTuple	seqtuple;
+	bool		reset_state = false;
+	bool		is_called;
+	int64		last_value;
 	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
@@ -481,12 +488,14 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	/* 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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
-				seqform, newdataform,
+				seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/* If needed, rewrite the sequence relation itself */
@@ -513,6 +522,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * 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);
 	}
 
@@ -1229,17 +1242,19 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 /*
  * init_params: process the options list of CREATE or ALTER SEQUENCE, and
  * store the values into appropriate fields of seqform, for changes that go
- * into the pg_sequence catalog, and fields of seqdataform for changes to the
- * sequence relation itself.  Set *need_seq_rewrite to true if we changed any
- * parameters that require rewriting the sequence's relation (interesting for
- * ALTER SEQUENCE).  Also set *owned_by to any OWNED BY option, or to NIL if
- * there is none.
+ * into the pg_sequence catalog, and fields for changes to the sequence
+ * relation itself (is_called, last_value or any state it may hold).  Set
+ * *need_seq_rewrite to true if we changed any parameters that require
+ * rewriting the sequence's relation (interesting for ALTER SEQUENCE).  Also
+ * set *owned_by to any OWNED BY option, or to NIL if there is none.  Set
+ * *reset_state if the internal state of the sequence needs to change on a
+ * follow-up nextval().
  *
  * If isInit is true, fill any unspecified options with default values;
  * otherwise, do not change existing options that aren't explicitly overridden.
  *
  * Note: we force a sequence rewrite whenever we change parameters that affect
- * generation of future sequence values, even if the seqdataform per se is not
+ * generation of future sequence values, even if the metadata per se is not
  * changed.  This allows ALTER SEQUENCE to behave transactionally.  Currently,
  * the only option that doesn't cause that is OWNED BY.  It's *necessary* for
  * ALTER SEQUENCE OWNED BY to not rewrite the sequence, because that would
@@ -1250,7 +1265,9 @@ static void
 init_params(ParseState *pstate, List *options, bool for_identity,
 			bool isInit,
 			Form_pg_sequence seqform,
-			Form_pg_sequence_data seqdataform,
+			int64 *last_value,
+			bool *reset_state,
+			bool *is_called,
 			bool *need_seq_rewrite,
 			List **owned_by)
 {
@@ -1353,11 +1370,11 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	}
 
 	/*
-	 * We must reset log_cnt when isInit or when changing any parameters that
-	 * would affect future nextval allocations.
+	 * We must reset the state when isInit or when changing any parameters
+	 * that would affect future nextval allocations.
 	 */
 	if (isInit)
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 
 	/* AS type */
 	if (as_type != NULL)
@@ -1406,7 +1423,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("INCREMENT must not be zero")));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1418,7 +1435,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	{
 		seqform->seqcycle = boolVal(is_cycled->arg);
 		Assert(BoolIsValid(seqform->seqcycle));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1429,7 +1446,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (max_value != NULL && max_value->arg)
 	{
 		seqform->seqmax = defGetInt64(max_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || max_value != NULL || reset_max_value)
 	{
@@ -1445,7 +1462,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmax = -1;	/* descending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate maximum value.  No need to check INT8 as seqmax is an int64 */
@@ -1461,7 +1478,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (min_value != NULL && min_value->arg)
 	{
 		seqform->seqmin = defGetInt64(min_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || min_value != NULL || reset_min_value)
 	{
@@ -1477,7 +1494,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmin = 1;	/* ascending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate minimum value.  No need to check INT8 as seqmin is an int64 */
@@ -1528,30 +1545,30 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (restart_value != NULL)
 	{
 		if (restart_value->arg != NULL)
-			seqdataform->last_value = defGetInt64(restart_value);
+			*last_value = defGetInt64(restart_value);
 		else
-			seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
-		seqdataform->log_cnt = 0;
+			*last_value = seqform->seqstart;
+		*is_called = false;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-		seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
+		*last_value = seqform->seqstart;
+		*is_called = false;
 	}
 
 	/* crosscheck RESTART (or current value, if changing MIN/MAX) */
-	if (seqdataform->last_value < seqform->seqmin)
+	if (*last_value < seqform->seqmin)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be less than MINVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmin)));
-	if (seqdataform->last_value > seqform->seqmax)
+	if (*last_value > seqform->seqmax) //here
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be greater than MAXVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmax)));
 
 	/* CACHE */
@@ -1563,7 +1580,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("CACHE (%lld) must be greater than zero",
 							(long long) seqform->seqcache)));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-- 
2.43.0

v1-0005-Integrate-addition-of-attributes-for-sequences-wi.patchtext/x-diff; charset=us-asciiDownload
From 48ba2f63917f2d4c48dbb4bd7085a4ca91830bac Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:16:13 +0900
Subject: [PATCH v1 5/9] 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               | 29 +++++++++++++++++--
 src/backend/commands/tablecmds.c              | 10 +++++++
 src/backend/tcop/utility.c                    |  4 +++
 .../test_ddl_deparse/expected/alter_table.out | 10 +++++--
 .../expected/create_sequence_1.out            |  5 +++-
 .../expected/create_table.out                 | 15 ++++++++--
 .../test_ddl_deparse/test_ddl_deparse.c       |  3 ++
 8 files changed, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e494309da8..6947225b64 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2186,6 +2186,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 8927f99670..bf6e867560 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -136,6 +136,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;
@@ -174,7 +177,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;
@@ -198,7 +200,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;
@@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 7206da7c53..b23c792f37 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4488,6 +4488,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 */
@@ -4782,6 +4783,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,
@@ -5195,6 +5203,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,
@@ -6347,6 +6356,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 e3ccf6c7f7..fb8d2e3668 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1668,7 +1668,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 ecde9d7422..3332954c97 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
@@ -71,7 +74,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 desc column a of table parent
diff --git a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
index 5837ea484e..310ce5a6ba 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.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 75b62aff4d..69e54358ee 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 alter table, tag ALTER TABLE
 NOTICE:    subcommand: type SET ATTNOTNULL desc <NULL>
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 0302f79bb7..ec07c173b8 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -114,6 +114,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.43.0

v1-0006-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From c7df01143c446193a799f7d2941ff3353f47ab64 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:02 +0900
Subject: [PATCH v1 6/9] Sequence access methods - backend support

---
 src/include/access/localam.h                  |  33 +
 src/include/access/rmgrlist.h                 |   2 +-
 src/include/access/sequenceam.h               | 188 +++++
 src/include/catalog/pg_am.dat                 |   3 +
 src/include/catalog/pg_am.h                   |   1 +
 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               |  34 -
 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/rmgrdesc/Makefile          |   2 +-
 .../rmgrdesc/{seqdesc.c => localseqdesc.c}    |  18 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/local.c           | 737 ++++++++++++++++++
 src/backend/access/sequence/meson.build       |   2 +
 src/backend/access/sequence/sequence.c        |   3 +-
 src/backend/access/sequence/sequenceamapi.c   | 145 ++++
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/catalog/heap.c                    |   3 +-
 src/backend/commands/amcmds.c                 |  16 +
 src/backend/commands/sequence.c               | 622 +--------------
 src/backend/commands/tablecmds.c              |  12 +-
 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            |  87 ++-
 src/backend/utils/misc/guc_tables.c           |  12 +
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 src/bin/psql/describe.c                       |   2 +
 src/bin/psql/tab-complete.c                   |   4 +-
 src/test/regress/expected/create_am.out       |  33 +-
 src/test/regress/expected/opr_sanity.out      |  12 +
 src/test/regress/expected/psql.out            |  64 +-
 src/test/regress/sql/create_am.sql            |  24 +-
 src/test/regress/sql/opr_sanity.sql           |  10 +
 src/tools/pgindent/typedefs.list              |   5 +-
 45 files changed, 1414 insertions(+), 718 deletions(-)
 create mode 100644 src/include/access/localam.h
 create mode 100644 src/include/access/sequenceam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => localseqdesc.c} (69%)
 create mode 100644 src/backend/access/sequence/local.c
 create mode 100644 src/backend/access/sequence/sequenceamapi.c

diff --git a/src/include/access/localam.h b/src/include/access/localam.h
new file mode 100644
index 0000000000..7afc0a9636
--- /dev/null
+++ b/src/include/access/localam.h
@@ -0,0 +1,33 @@
+/*-------------------------------------------------------------------------
+ *
+ * localam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/localam.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef LOCALAM_H
+#define LOCALAM_H
+
+#include "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+
+/* XLOG stuff */
+#define XLOG_LOCAL_SEQ_LOG			0x00
+
+typedef struct xl_local_seq_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_local_seq_rec;
+
+extern void local_seq_redo(XLogReaderState *record);
+extern void local_seq_desc(StringInfo buf, XLogReaderState *record);
+extern const char *local_seq_identify(uint8 info);
+extern void local_seq_mask(char *page, BlockNumber blkno);
+
+#endif							/* LOCALAM_H */
diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 463bcb67c5..544997b01d 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_LOCAL_SEQ_ID, "LocalSequence", local_seq_redo, local_seq_desc, local_seq_identify, NULL, NULL, local_seq_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/sequenceam.h b/src/include/access/sequenceam.h
new file mode 100644
index 0000000000..4052f61269
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,188 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2023, 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 "local"
+
+/* 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_sequences.
+	 */
+	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_sequences.  "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_sequences.
+	 */
+	void		(*reset) (Relation rel, int64 startv, bool is_called,
+						  bool reset_state);
+
+	/*
+	 * Returns the currenr 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.
+	 */
+	void		(*get_state) (Relation rel, int64 *last_value, bool *is_called);
+
+	/*
+	 * 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_sequences.
+ */
+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)
+{
+	rel->rd_sequenceam->get_state(rel, last_value, is_called);
+}
+
+/*
+ * 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);
+
+/* ----------------------------------------------------------------------------
+ * Functions in local.c
+ * ----------------------------------------------------------------------------
+ */
+
+extern const SequenceAmRoutine *GetLocalSequenceAmRoutine(void);
+
+#endif							/* SEQUENCEAM_H */
diff --git a/src/include/catalog/pg_am.dat b/src/include/catalog/pg_am.dat
index ed641037dd..6de9f0f23d 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 => '8047', oid_symbol => 'LOCAL_SEQUENCE_AM_OID',
+  descr => 'local sequence access method',
+  amname => 'local', amhandler => '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 d5314bb38b..2d10b9c18a 100644
--- a/src/include/catalog/pg_am.h
+++ b/src/include/catalog/pg_am.h
@@ -56,6 +56,7 @@ DECLARE_UNIQUE_INDEX_PKEY(pg_am_oid_index, 2652, AmOidIndexId, pg_am, btree(oid
  * 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_proc.dat b/src/include/catalog/pg_proc.dat
index 5999952da3..63bdd117ac 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 => 'local_sequenceam_handler', provolatile => 'v',
+  prorettype => 'sequence_am_handler', proargtypes => 'internal',
+  prosrc => 'local_sequenceam_handler' },
+
 # Index access method handlers
 { oid => '330', descr => 'btree index access method handler',
   proname => 'bthandler', provolatile => 'v', prorettype => 'index_am_handler',
@@ -7598,6 +7604,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 f6110a850d..46d70c4cc4 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -626,6 +626,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 179eb9901f..4a6329ee51 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -141,6 +141,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 7db7b3da7b..1ff652848d 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index 626dc696d5..4a5ab87c2f 100644
--- a/src/include/nodes/meson.build
+++ b/src/include/nodes/meson.build
@@ -9,6 +9,7 @@ node_support_input_i = [
   '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/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6947225b64..aab9bfa709 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2972,6 +2972,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 3d74483f44..c13b8955a2 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -53,6 +53,8 @@ extern bool check_debug_io_direct(char **newval, void **extra, GucSource source)
 extern void assign_debug_io_direct(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 0ad613c4b8..df226ad6f2 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -187,6 +187,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/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile
index cd95eec37f..dff5a60e68 100644
--- a/src/backend/access/rmgrdesc/Makefile
+++ b/src/backend/access/rmgrdesc/Makefile
@@ -18,13 +18,13 @@ OBJS = \
 	gistdesc.o \
 	hashdesc.o \
 	heapdesc.o \
+	localseqdesc.o \
 	logicalmsgdesc.o \
 	mxactdesc.o \
 	nbtdesc.o \
 	relmapdesc.o \
 	replorigindesc.o \
 	rmgrdesc_utils.o \
-	seqdesc.o \
 	smgrdesc.o \
 	spgdesc.o \
 	standbydesc.o \
diff --git a/src/backend/access/rmgrdesc/seqdesc.c b/src/backend/access/rmgrdesc/localseqdesc.c
similarity index 69%
rename from src/backend/access/rmgrdesc/seqdesc.c
rename to src/backend/access/rmgrdesc/localseqdesc.c
index ba60544085..3e8dfda01f 100644
--- a/src/backend/access/rmgrdesc/seqdesc.c
+++ b/src/backend/access/rmgrdesc/localseqdesc.c
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
- * seqdesc.c
- *	  rmgr descriptor routines for commands/sequence.c
+ * localseqdesc.c
+ *	  rmgr descriptor routines for sequence/local.c
  *
  * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -14,31 +14,31 @@
  */
 #include "postgres.h"
 
-#include "commands/sequence.h"
+#include "access/localam.h"
 
 
 void
-seq_desc(StringInfo buf, XLogReaderState *record)
+local_seq_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_local_seq_rec *xlrec = (xl_local_seq_rec *) rec;
 
-	if (info == XLOG_SEQ_LOG)
+	if (info == XLOG_LOCAL_SEQ_LOG)
 		appendStringInfo(buf, "rel %u/%u/%u",
 						 xlrec->locator.spcOid, xlrec->locator.dbOid,
 						 xlrec->locator.relNumber);
 }
 
 const char *
-seq_identify(uint8 info)
+local_seq_identify(uint8 info)
 {
 	const char *id = NULL;
 
 	switch (info & ~XLR_INFO_MASK)
 	{
-		case XLOG_SEQ_LOG:
-			id = "LOG";
+		case XLOG_LOCAL_SEQ_LOG:
+			id = "LOCAL_SEQ_LOG";
 			break;
 	}
 
diff --git a/src/backend/access/rmgrdesc/meson.build b/src/backend/access/rmgrdesc/meson.build
index f76e87e2d7..46fbf12730 100644
--- a/src/backend/access/rmgrdesc/meson.build
+++ b/src/backend/access/rmgrdesc/meson.build
@@ -11,13 +11,13 @@ rmgr_desc_sources = files(
   'gistdesc.c',
   'hashdesc.c',
   'heapdesc.c',
+  'localseqdesc.c',
   'logicalmsgdesc.c',
   'mxactdesc.c',
   'nbtdesc.c',
   'relmapdesc.c',
   'replorigindesc.c',
   'rmgrdesc_utils.c',
-  'seqdesc.c',
   'smgrdesc.c',
   'spgdesc.c',
   'standbydesc.c',
diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile
index 9f9d31f542..b89a7e0526 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = local.o sequence.o sequenceamapi.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/sequence/local.c b/src/backend/access/sequence/local.c
new file mode 100644
index 0000000000..459a4e4301
--- /dev/null
+++ b/src/backend/access/sequence/local.c
@@ -0,0 +1,737 @@
+/*-------------------------------------------------------------------------
+ *
+ * local.c
+ *	  Local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/access/sequence/local.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/htup_details.h"
+#include "access/localam.h"
+#include "access/multixact.h"
+#include "access/reloptions.h"
+#include "access/sequenceam.h"
+#include "access/xact.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/pg_type.h"
+#include "catalog/storage_xlog.h"
+#include "commands/defrem.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "storage/bufmgr.h"
+#include "utils/builtins.h"
+#include "utils/rel.h"
+#include "utils/typcache.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 LOCAL_SEQ_LOG_VALS	32
+
+/*
+ * The "special area" of a local sequence's buffer page looks like this.
+ */
+#define LOCAL_SEQ_MAGIC	  0x1717
+
+typedef struct local_sequence_magic
+{
+	uint32		magic;
+} local_sequence_magic;
+
+/* Format of tuples stored in heap table associated to local sequences */
+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 local 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
+
+
+/*
+ * 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
+
+static Form_pg_sequence_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_sequence_data
+read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
+{
+	Page		page;
+	ItemId		lp;
+	local_sequence_magic *sm;
+	Form_pg_sequence_data seq;
+
+	*buf = ReadBuffer(rel, 0);
+	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(*buf);
+	sm = (local_sequence_magic *) PageGetSpecialPointer(page);
+
+	if (sm->magic != LOCAL_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;
+}
+
+/*
+ * 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, InvalidBackendId);
+		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;
+	local_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(local_sequence_magic));
+	sm = (local_sequence_magic *) PageGetSpecialPointer(page);
+	sm->magic = LOCAL_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, (Item) 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_local_seq_rec xlrec;
+		XLogRecPtr	recptr;
+
+		XLogBeginInsert();
+		XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
+
+		xlrec.locator = rel->rd_locator;
+
+		XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec));
+		XLogRegisterData((char *) tuple->t_data, tuple->t_len);
+
+		recptr = XLogInsert(RM_LOCAL_SEQ_ID, XLOG_LOCAL_SEQ_LOG);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buf);
+}
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+local_seq_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+local_seq_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_local_seq_rec *xlrec = (xl_local_seq_rec *) XLogRecGetData(record);
+	local_sequence_magic *sm;
+
+	if (info != XLOG_LOCAL_SEQ_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = (Page) BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	PageInit(localpage, BufferGetPageSize(buffer), sizeof(local_sequence_magic));
+	sm = (local_sequence_magic *) PageGetSpecialPointer(localpage);
+	sm->magic = LOCAL_SEQ_MAGIC;
+
+	item = (char *) xlrec + sizeof(xl_local_seq_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_local_seq_rec);
+
+	if (PageAddItem(localpage, (Item) item, itemsz,
+					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "local_seq_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
+
+/*
+ * local_nextval()
+ *
+ * Allocate a new value for a local sequence, based on the sequence
+ * configuration.
+ */
+static int64
+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_sequence_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_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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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_local_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 = rel->rd_locator;
+
+		XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec));
+		XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len);
+
+		recptr = XLogInsert(RM_LOCAL_SEQ_ID, XLOG_LOCAL_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);
+
+	return result;
+}
+
+/*
+ * local_get_table_am()
+ *
+ * Return the table access method used by this sequence.
+ */
+static const char *
+local_get_table_am(void)
+{
+	return "heap";
+}
+
+/*
+ * 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.
+ */
+static void
+local_init(Relation rel, int64 last_value, bool is_called)
+{
+	Datum		value[SEQ_COL_LASTCOL];
+	bool		null[SEQ_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_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(0);
+				break;
+			case SEQ_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);
+}
+
+/*
+ * local_setval()
+ *
+ * Callback for setval().
+ */
+static void
+local_setval(Relation rel, int64 next, bool iscalled)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_sequence_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_local_seq_rec xlrec;
+		XLogRecPtr	recptr;
+		Page		page = BufferGetPage(buf);
+
+		XLogBeginInsert();
+		XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
+
+		xlrec.locator = rel->rd_locator;
+		XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec));
+		XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len);
+
+		recptr = XLogInsert(RM_LOCAL_SEQ_ID, XLOG_LOCAL_SEQ_LOG);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buf);
+}
+
+/*
+ * local_reset()
+ *
+ * Perform a hard reset on the local sequence, rewriting its heap data
+ * entirely.
+ */
+static void
+local_reset(Relation rel, int64 startv, bool is_called, bool reset_state)
+{
+	Form_pg_sequence_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_sequence_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);
+}
+
+/*
+ * local_get_state()
+ *
+ * Retrieve the state of a local sequence.
+ */
+static void
+local_get_state(Relation rel, int64 *last_value, bool *is_called)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_sequence_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	*last_value = seq->last_value;
+	*is_called = seq->is_called;
+
+	UnlockReleaseBuffer(buf);
+}
+
+/*
+ * local_change_persistence()
+ *
+ * Persistence change for the local sequence Relation.
+ */
+static void
+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);
+}
+
+/* ------------------------------------------------------------------------
+ * Definition of the local sequence access method.
+ * ------------------------------------------------------------------------
+ */
+static const SequenceAmRoutine local_methods = {
+	.type = T_SequenceAmRoutine,
+	.get_table_am = local_get_table_am,
+	.init = local_init,
+	.nextval = local_nextval,
+	.setval = local_setval,
+	.reset = local_reset,
+	.get_state = local_get_state,
+	.change_persistence = local_change_persistence
+};
+
+Datum
+local_sequenceam_handler(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_POINTER(&local_methods);
+}
diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build
index 1840a913bc..feae8e5884 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2023, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'local.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/sequence.c b/src/backend/access/sequence/sequence.c
index a5b9fccb50..64e023f0b4 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 0000000000..4314b9ecb5
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2023, 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/xact.h"
+#include "access/sequenceam.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/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 7d67eda5f7..c3f9acb064 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -15,6 +15,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/localam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 7224d96695..6aa0a3f9e7 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1452,7 +1452,8 @@ 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.
 		 */
-		if (RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE)
+		if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE) ||
+			relkind == RELKIND_SEQUENCE)
 		{
 			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 2050619123..688b2163d3 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 bf6e867560..24a515441a 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -19,6 +19,7 @@
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -50,23 +51,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -96,13 +80,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,
@@ -134,14 +114,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
@@ -174,39 +148,11 @@ 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;
 	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;
@@ -215,35 +161,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	/* now initialize the sequence table structure and its data */
+	sequence_init(rel, last_value, is_called);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -292,10 +223,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;
@@ -306,7 +233,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))
@@ -315,40 +241,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. */
+	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 */
@@ -357,106 +251,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, InvalidBackendId);
-		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, (Item) 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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) tuple->t_data, tuple->t_len);
-
-		recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	UnlockReleaseBuffer(buf);
-}
-
 /*
  * AlterSequence
  *
@@ -468,10 +262,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;
@@ -480,7 +271,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	bool		reset_state = false;
 	bool		is_called;
 	int64		last_value;
-	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
 	relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -507,16 +297,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 */
+	sequence_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -526,32 +308,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);
+		sequence_reset(seqrel, last_value, is_called, reset_state);
 	}
 
 	/* Clear local cache so that we don't think we have cached numbers */
@@ -580,8 +340,6 @@ SequenceChangePersistence(Oid relid, char newrelpersistence)
 {
 	SeqTable	elm;
 	Relation	seqrel;
-	Buffer		buf;
-	HeapTupleData seqdatatuple;
 
 	init_sequence(relid, &elm, &seqrel);
 
@@ -589,10 +347,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);
+	sequence_change_persistence(seqrel, newrelpersistence);
 
 	sequence_close(seqrel, NoLock);
 }
@@ -655,24 +410,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);
@@ -717,105 +463,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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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 = sequence_nextval(seqrel, incby, maxv, minv, cache, cycle,
+							  &last);
 
 	/* save info in local cache */
 	elm->increment = incby;
@@ -825,69 +475,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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) 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;
@@ -977,9 +564,6 @@ do_setval(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,
@@ -1013,9 +597,6 @@ do_setval(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),
@@ -1037,37 +618,8 @@ do_setval(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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) 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 */
+	sequence_setval(seqrel, next, iscalled);
 
 	sequence_close(seqrel, NoLock);
 }
@@ -1208,62 +760,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
@@ -1589,7 +1085,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 				 errmsg("RESTART value (%lld) cannot be less than MINVALUE (%lld)",
 						(long long) *last_value,
 						(long long) seqform->seqmin)));
-	if (*last_value > seqform->seqmax) //here
+	if (*last_value > seqform->seqmax)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be greater than MAXVALUE (%lld)",
@@ -1823,9 +1319,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	SeqTable	elm;
 	Relation	seqrel;
 	TupleDesc	tupdesc;
-	Buffer		buf;
-	HeapTupleData seqtuple;
-	Form_pg_sequence_data seq;
 	bool		is_called;
 	int64		last_value;
 
@@ -1842,12 +1335,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 				 errmsg("permission denied for sequence %s",
 						RelationGetRelationName(seqrel))));
 
-	seq = read_seq_tuple(seqrel, &buf, &seqtuple);
-
-	is_called = seq->is_called;
-	last_value = seq->last_value;
-
-	UnlockReleaseBuffer(buf);
+	sequence_get_state(seqrel, &last_value, &is_called);
 	sequence_close(seqrel, NoLock);
 
 	values[0] = BoolGetDatum(is_called);
@@ -1855,57 +1343,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
 }
 
-
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = (Page) BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, (Item) item, itemsz,
-					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1920,14 +1357,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b23c792f37..76ce4596b0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -22,6 +22,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"
@@ -957,10 +958,17 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	}
 	else if (RELKIND_HAS_TABLE_AM(relkind))
 		accessMethod = default_table_access_method;
+	else if (relkind == RELKIND_SEQUENCE)
+		accessMethod = default_sequence_access_method;
 
-	/* look up the access method, verify it is for a table */
+	/* look up the access method, verify it is for a table or a sequence */
 	if (accessMethod != NULL)
-		accessMethodId = get_table_am_oid(accessMethod, false);
+	{
+		if (relkind == RELKIND_SEQUENCE)
+			accessMethodId = get_sequence_am_oid(accessMethod, false);
+		else
+			accessMethodId = get_table_am_oid(accessMethod, false);
+	}
 
 	/*
 	 * Create the relation.  Inherited defaults and constraints are passed in
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index ebbe9052cb..31dfd9c233 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -48,6 +48,7 @@ node_headers = \
 	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/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 72c7963578..b642eca278 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -59,6 +59,7 @@ my @all_input_files = qw(
   nodes/execnodes.h
   access/amapi.h
   access/sdir.h
+  access/sequenceam.h
   access/tableam.h
   access/tsmapi.h
   commands/event_trigger.h
@@ -83,6 +84,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 d631ac89a9..23c06daca6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -388,6 +388,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
@@ -4753,23 +4754,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;
@@ -4806,6 +4810,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); }
 		;
@@ -5802,6 +5811,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 cf0d432ab1..1e1633a01c 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"
@@ -461,6 +462,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
 	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 3ba8cb192c..15e065d755 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -372,6 +372,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 b3faccbefe..8504bb52f7 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -35,6 +35,7 @@
 #include "access/nbtree.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"
@@ -66,6 +67,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"
@@ -301,6 +303,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);
@@ -1206,9 +1209,10 @@ 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_SEQUENCE)
+		RelationInitSequenceAccessMethod(relation);
 	else
 		Assert(relation->rd_rel->relam == InvalidOid);
 
@@ -1805,17 +1809,7 @@ 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))
+	if (IsCatalogRelation(relation))
 	{
 		/*
 		 * Avoid doing a syscache lookup for catalog tables.
@@ -1846,6 +1840,47 @@ 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;
+
+	/*
+	 * 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
  *
@@ -3687,14 +3722,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);
 
 	/*
 	 * Okay to insert into the relcache hash table.
@@ -4307,13 +4345,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);
@@ -6313,8 +6359,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);
@@ -6326,6 +6374,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_tables.c b/src/backend/utils/misc/guc_tables.c
index 6474e35ec0..6d54deda8d 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -29,6 +29,7 @@
 #include "access/commit_ts.h"
 #include "access/gin.h"
 #include "access/toast_compression.h"
+#include "access/sequenceam.h"
 #include "access/twophase.h"
 #include "access/xlog_internal.h"
 #include "access/xlogprefetcher.h"
@@ -3999,6 +4000,17 @@ struct config_string ConfigureNamesString[] =
 		check_default_table_access_method, NULL, NULL
 	},
 
+	{
+		{"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets the default sequence access method for new sequences."),
+			NULL,
+			GUC_IS_NAME
+		},
+		&default_sequence_access_method,
+		DEFAULT_SEQUENCE_ACCESS_METHOD,
+		check_default_sequence_access_method, NULL, NULL
+	},
+
 	{
 		{"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default tablespace to create tables and indexes in."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf9f283cfe..e3e46923cf 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -684,6 +684,7 @@
 					#   error
 #search_path = '"$user", public'	# schema names
 #row_security = on
+#default_sequence_access_method = 'local'
 #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/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c76..0f45509f2c 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/localseqdesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index 6b8c17bb4c..ff09335607 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/localam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index 029a0d0521..7be589d92e 100644
--- a/src/bin/pg_waldump/t/001_basic.pl
+++ b/src/bin/pg_waldump/t/001_basic.pl
@@ -41,7 +41,7 @@ Btree
 Hash
 Gin
 Gist
-Sequence
+LocalSequence
 SPGist
 BRIN
 CommitTs
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 5077e7b358..aa8c6c73e6 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -161,10 +161,12 @@ describeAccessMethods(const char *pattern, bool verbose)
 					  "SELECT amname AS \"%s\",\n"
 					  "  CASE amtype"
 					  " WHEN 'i' THEN '%s'"
+					  " WHEN 's' THEN '%s'"
 					  " WHEN 't' 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.c b/src/bin/psql/tab-complete.c
index 049801186c..752208b6a2 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2197,7 +2197,7 @@ psql_completion(const char *text, int start, int end)
 	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");
@@ -3204,7 +3204,7 @@ psql_completion(const char *text, int start, int end)
 	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 b50293d514..dfcc9cff49 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;
@@ -331,9 +326,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 = 'local';
 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;
@@ -356,7 +354,7 @@ ORDER BY 3, 1, 2;
  r       | heap2  | tableam_parted_1_heapx
  r       | heap   | tableam_parted_2_heapx
  p       |        | tableam_parted_heapx
- S       |        | tableam_seq_heapx
+ S       | local  | tableam_seq_heapx
  r       | heap2  | tableam_tbl_heapx
  r       | heap2  | tableam_tblas_heapx
  m       | heap2  | tableam_tblmv_heapx
@@ -388,3 +386,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 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 7610b011d6..12f48e4beb 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1929,6 +1929,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 13e4f6db7b..7987dd0586 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4958,8 +4958,8 @@ Indexes:
 -- check printing info about access methods
 \dA
 List of access methods
-  Name  | Type  
---------+-------
+  Name  |   Type   
+--------+----------
  brin   | Index
  btree  | Index
  gin    | Index
@@ -4967,13 +4967,14 @@ List of access methods
  hash   | Index
  heap   | Table
  heap2  | Table
+ local  | Sequence
  spgist | Index
-(8 rows)
+(9 rows)
 
 \dA *
 List of access methods
-  Name  | Type  
---------+-------
+  Name  |   Type   
+--------+----------
  brin   | Index
  btree  | Index
  gin    | Index
@@ -4981,8 +4982,9 @@ List of access methods
  hash   | Index
  heap   | Table
  heap2  | Table
+ local  | Sequence
  spgist | Index
-(8 rows)
+(9 rows)
 
 \dA h*
 List of access methods
@@ -5007,32 +5009,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     | 
+ local  | Sequence | 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     | 
+ local  | Sequence | 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/sql/create_am.sql b/src/test/regress/sql/create_am.sql
index 2785ffd8bb..6b180519aa 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;
@@ -222,9 +219,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 = 'local';
 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;
@@ -257,3 +258,16 @@ CREATE TABLE i_am_a_failure() USING "btree";
 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 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 2fe7b6dcc4..1409622374 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1229,6 +1229,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/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index d659adbfd6..7aa3ba978e 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2492,6 +2492,7 @@ SeqScan
 SeqScanState
 SeqTable
 SeqTableData
+SequenceAmRoutine
 SerCommitSeqNo
 SerialControl
 SerialIOData
@@ -3462,6 +3463,7 @@ lineno_t
 list_sort_comparator
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 locale_t
 locate_agg_of_level_context
@@ -3725,7 +3727,6 @@ save_buffer
 scram_state
 scram_state_enum
 sem_t
-sequence_magic
 set_join_pathlist_hook_type
 set_rel_pathlist_hook_type
 shm_mq
@@ -3942,6 +3943,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
@@ -3953,7 +3955,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.43.0

v1-0007-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From b8ed9b7221692110d8ce51b57b751f72e0d102fc Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v1 7/9] 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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 94d1eb2b81..f4d527efed 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8707,6 +8707,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 bd42b3ef16..62a4a233b4 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -96,6 +96,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 f31dc2094a..eefa350e1d 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;
   &generic-wal;
   &custom-rmgr;
   &btree;
diff --git a/doc/src/sgml/ref/create_access_method.sgml b/doc/src/sgml/ref/create_access_method.sgml
index dae43dbaed..3067dc4d4d 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>SEQUENCE</literal> and
+      <literal>INDEX</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 34e9084b5c..d00bdabde0 100644
--- a/doc/src/sgml/ref/create_sequence.sgml
+++ b/doc/src/sgml/ref/create_sequence.sgml
@@ -27,6 +27,7 @@ CREATE [ { TEMPORARY | TEMP } | UNLOGGED ] SEQUENCE [ IF NOT EXISTS ] <replaceab
     [ MINVALUE <replaceable class="parameter">minvalue</replaceable> | NO MINVALUE ] [ MAXVALUE <replaceable class="parameter">maxvalue</replaceable> | NO MAXVALUE ]
     [ START [ WITH ] <replaceable class="parameter">start</replaceable> ] [ CACHE <replaceable class="parameter">cache</replaceable> ] [ [ NO ] CYCLE ]
     [ OWNED BY { <replaceable class="parameter">table_name</replaceable>.<replaceable class="parameter">column_name</replaceable> | NONE } ]
+    [ USING <replaceable class="parameter">access_method</replaceable> ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -261,6 +262,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 0000000000..a96170bfac
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.43.0

v1-0008-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From 10d56dcc2152d92a5f066ddcd856972b3278405e Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:56:26 +0900
Subject: [PATCH v1 8/9] 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            | 39 ++++++++++++++--
 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     | 63 ++++++++++++++++++++++----
 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, 210 insertions(+), 14 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 9ef2f2017e..a838e6490c 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -95,6 +95,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
@@ -183,6 +184,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 256d1e35a4..0899e3bea9 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -171,6 +171,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;
@@ -1118,6 +1119,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->owner = opts->owner ? pg_strdup(opts->owner) : NULL;
 	newToc->desc = pg_strdup(opts->description);
@@ -2258,6 +2260,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 */
 
@@ -2487,6 +2490,7 @@ WriteToc(ArchiveHandle *AH)
 		WriteStr(AH, te->copyStmt);
 		WriteStr(AH, te->namespace);
 		WriteStr(AH, te->tablespace);
+		WriteStr(AH, te->sequenceam);
 		WriteStr(AH, te->tableam);
 		WriteStr(AH, te->owner);
 		WriteStr(AH, "false");
@@ -2590,6 +2594,9 @@ ReadToc(ArchiveHandle *AH)
 		if (AH->version >= K_VERS_1_10)
 			te->tablespace = ReadStr(AH);
 
+		if (AH->version >= K_VERS_1_16)
+			te->sequenceam = ReadStr(AH);
+
 		if (AH->version >= K_VERS_1_14)
 			te->tableam = ReadStr(AH);
 
@@ -3226,6 +3233,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;
 
@@ -3388,6 +3398,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.
  */
@@ -3547,6 +3608,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
 	_becomeOwner(AH, te);
 	_selectOutputSchema(AH, te->namespace);
 	_selectTablespace(AH, te->tablespace);
+	_selectSequenceAccessMethod(AH, te->sequenceam);
 	_selectTableAccessMethod(AH, te->tableam);
 
 	/* Emit header comment for item */
@@ -4003,6 +4065,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);
@@ -4738,6 +4802,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -4787,6 +4852,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 917283fd34..d29a22ac40 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -68,10 +68,11 @@
 #define K_VERS_1_15 MAKE_ARCHIVE_VERSION(1, 15, 0)	/* add
 													 * compression_algorithm
 													 * in header */
+#define K_VERS_1_16 MAKE_ARCHIVE_VERSION(1, 16, 0)	/* add sequenceam */
 
 /* Current archive version number (the format we can output) */
 #define K_VERS_MAJOR 1
-#define K_VERS_MINOR 15
+#define K_VERS_MINOR 16
 #define K_VERS_REV 0
 #define K_VERS_SELF MAKE_ARCHIVE_VERSION(K_VERS_MAJOR, K_VERS_MINOR, K_VERS_REV)
 
@@ -319,6 +320,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 */
 
@@ -347,6 +349,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	   *owner;
 	char	   *desc;
@@ -387,6 +390,7 @@ typedef struct _archiveOpts
 	const char *tag;
 	const char *namespace;
 	const char *tablespace;
+	const char *sequenceam;
 	const char *tableam;
 	const char *owner;
 	const char *description;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c7612c3793..7e3da007da 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -411,6 +411,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},
@@ -1015,6 +1016,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;
@@ -1130,6 +1132,7 @@ help(const char *progname)
 	printf(_("  --no-publications            do not dump publications\n"));
 	printf(_("  --no-security-labels         do not dump security label assignments\n"));
 	printf(_("  --no-subscriptions           do not dump subscriptions\n"));
+	printf(_("  --no-sequence-access-method  do not 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"));
@@ -12995,6 +12998,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;
@@ -17211,7 +17217,8 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 			   *maxv,
 			   *minv,
 			   *cache,
-			   *seqtype;
+			   *seqtype,
+			   *seqam;
 	bool		cycled;
 	bool		is_ascending;
 	int64		default_minv,
@@ -17225,13 +17232,35 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
-	if (fout->remoteVersion >= 100000)
+	if (fout->remoteVersion >= 170000)
 	{
+		/*
+		 * PostgreSQL 17 has added support for sequence access methods.
+		 */
+		appendPQExpBuffer(query,
+						  "SELECT format_type(s.seqtypid, NULL), "
+						  "s.seqstart, s.seqincrement, "
+						  "s.seqmax, s.seqmin, "
+						  "s.seqcache, s.seqcycle, "
+						  "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) "
+						  "WHERE s.seqrelid = '%u'::oid",
+						  tbinfo->dobj.catId.oid);
+	}
+	else if (fout->remoteVersion >= 100000)
+	{
+		/*
+		 * PostgreSQL 10 has moved sequence metadata to the catalog
+		 * pg_sequence.
+		 */
 		appendPQExpBuffer(query,
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
+						  "seqcache, seqcycle, "
+						  "'local' AS seqam "
 						  "FROM pg_catalog.pg_sequence "
 						  "WHERE seqrelid = '%u'::oid",
 						  tbinfo->dobj.catId.oid);
@@ -17247,7 +17276,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 		appendPQExpBuffer(query,
 						  "SELECT 'bigint' AS sequence_type, "
 						  "start_value, increment_by, max_value, min_value, "
-						  "cache_value, is_cycled FROM %s",
+						  "cache_value, is_cycled, 'local' as seqam FROM %s",
 						  fmtQualifiedDumpable(tbinfo));
 	}
 
@@ -17266,6 +17295,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	minv = PQgetvalue(res, 0, 4);
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
+	seqam = PQgetvalue(res, 0, 7);
 
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
@@ -17397,6 +17427,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 					 ARCHIVE_OPTS(.tag = tbinfo->dobj.name,
 								  .namespace = tbinfo->dobj.namespace->dobj.name,
 								  .owner = tbinfo->rolname,
+								  .sequenceam = 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 92389353a4..5c96f3eee1 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -99,6 +99,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;
@@ -163,6 +164,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},
@@ -437,6 +439,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)
@@ -670,6 +674,7 @@ help(void)
 	printf(_("  --no-security-labels         do not dump security label assignments\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 c3beacdec1..0049130535 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -68,6 +68,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;
@@ -115,6 +116,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},
@@ -351,6 +353,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;
@@ -479,6 +482,7 @@ usage(const char *progname)
 	printf(_("  --no-publications            do not restore publications\n"));
 	printf(_("  --no-security-labels         do not restore security labels\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(_("  --section=SECTION            restore named section (pre-data, data, or post-data)\n"));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index eb3ec534b4..6557e44ba6 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -548,6 +548,13 @@ 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', 'postgres',
+		],
+	},
 	no_table_access_method => {
 		dump_cmd => [
 			'pg_dump', '--no-sync',
@@ -722,6 +729,7 @@ my %full_runs = (
 	no_large_objects => 1,
 	no_owner => 1,
 	no_privs => 1,
+	no_sequence_access_method => 1,
 	no_table_access_method => 1,
 	pg_dumpall_dbprivs => 1,
 	pg_dumpall_exclude => 1,
@@ -3829,9 +3837,7 @@ my %tests = (
 		\QCREATE INDEX measurement_city_id_logdate_idx ON ONLY dump_test.measurement USING\E
 		/xm,
 		like => {
-			%full_runs,
-			%dump_test_schema_runs,
-			section_post_data => 1,
+			%full_runs, %dump_test_schema_runs, section_post_data => 1,
 		},
 		unlike => {
 			exclude_dump_test_schema => 1,
@@ -4500,6 +4506,18 @@ my %tests = (
 		},
 	},
 
+	'CREATE ACCESS METHOD regress_test_sequence_am' => {
+		create_order => 11,
+		create_sql =>
+		  'CREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER local_sequenceam_handler;',
+		regexp => qr/^
+			\QCREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER 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
@@ -4528,6 +4546,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 local;
+			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 local;',
+		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 => '
@@ -4722,10 +4769,8 @@ $node->command_fails_like(
 ##############################################################
 # Test dumping pg_catalog (for research -- cannot be reloaded)
 
-$node->command_ok(
-	[ 'pg_dump', '-p', "$port", '-n', 'pg_catalog' ],
-	'pg_dump: option -n pg_catalog'
-);
+$node->command_ok([ 'pg_dump', '-p', "$port", '-n', 'pg_catalog' ],
+	'pg_dump: option -n pg_catalog');
 
 #########################################
 # Test valid database exclusion patterns
@@ -4887,8 +4932,8 @@ foreach my $run (sort keys %pgdump_runs)
 		}
 		# Check for useless entries in "unlike" list.  Runs that are
 		# not listed in "like" don't need to be excluded in "unlike".
-		if ($tests{$test}->{unlike}->{$test_key} &&
-			!defined($tests{$test}->{like}->{$test_key}))
+		if ($tests{$test}->{unlike}->{$test_key}
+			&& !defined($tests{$test}->{like}->{$test_key}))
 		{
 			die "useless \"unlike\" entry \"$test_key\" in test \"$test\"";
 		}
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 0e5ba4f712..0dd0f5e0b4 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1083,6 +1083,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 4d7c046468..34643175fb 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -479,6 +479,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 1a23874da6..6581cff721 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -733,6 +733,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.43.0

v1-0009-dummy_sequence_am-Example-of-sequence-AM.patchtext/x-diff; charset=us-asciiDownload
From 81c7a600ed62818e7c81e000d0876e959c6001d6 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:56:52 +0900
Subject: [PATCH v1 9/9] dummy_sequence_am: Example of sequence AM

---
 src/test/modules/Makefile                     |   1 +
 src/test/modules/dummy_sequence_am/.gitignore |   3 +
 src/test/modules/dummy_sequence_am/Makefile   |  19 +++
 .../dummy_sequence_am--1.0.sql                |  13 +++
 .../dummy_sequence_am/dummy_sequence_am.c     | 110 ++++++++++++++++++
 .../dummy_sequence_am.control                 |   5 +
 .../expected/dummy_sequence.out               |  35 ++++++
 .../modules/dummy_sequence_am/meson.build     |  33 ++++++
 .../dummy_sequence_am/sql/dummy_sequence.sql  |  17 +++
 src/test/modules/meson.build                  |   1 +
 10 files changed, 237 insertions(+)
 create mode 100644 src/test/modules/dummy_sequence_am/.gitignore
 create mode 100644 src/test/modules/dummy_sequence_am/Makefile
 create mode 100644 src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql
 create mode 100644 src/test/modules/dummy_sequence_am/dummy_sequence_am.c
 create mode 100644 src/test/modules/dummy_sequence_am/dummy_sequence_am.control
 create mode 100644 src/test/modules/dummy_sequence_am/expected/dummy_sequence.out
 create mode 100644 src/test/modules/dummy_sequence_am/meson.build
 create mode 100644 src/test/modules/dummy_sequence_am/sql/dummy_sequence.sql

diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 5d33fa6a9a..81f857599e 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -10,6 +10,7 @@ SUBDIRS = \
 		  delay_execution \
 		  dummy_index_am \
 		  dummy_seclabel \
+		  dummy_sequence_am \
 		  libpq_pipeline \
 		  plsample \
 		  spgist_name_ops \
diff --git a/src/test/modules/dummy_sequence_am/.gitignore b/src/test/modules/dummy_sequence_am/.gitignore
new file mode 100644
index 0000000000..44d119cfcc
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/.gitignore
@@ -0,0 +1,3 @@
+# Generated subdirectories
+/log/
+/results/
diff --git a/src/test/modules/dummy_sequence_am/Makefile b/src/test/modules/dummy_sequence_am/Makefile
new file mode 100644
index 0000000000..391f7ac946
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/Makefile
@@ -0,0 +1,19 @@
+# src/test/modules/dummy_sequence_am/Makefile
+
+MODULES = dummy_sequence_am
+
+EXTENSION = dummy_sequence_am
+DATA = dummy_sequence_am--1.0.sql
+
+REGRESS = dummy_sequence
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/dummy_sequence_am
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql b/src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql
new file mode 100644
index 0000000000..e12b1f9d87
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql
@@ -0,0 +1,13 @@
+/* src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION dummy_sequence_am" to load this file. \quit
+
+CREATE FUNCTION dummy_sequenceam_handler(internal)
+  RETURNS sequence_am_handler
+  AS 'MODULE_PATHNAME'
+  LANGUAGE C;
+
+CREATE ACCESS METHOD dummy_sequence_am
+  TYPE SEQUENCE HANDLER dummy_sequenceam_handler;
+COMMENT ON ACCESS METHOD dummy_sequence_am IS 'dummy sequence access method';
diff --git a/src/test/modules/dummy_sequence_am/dummy_sequence_am.c b/src/test/modules/dummy_sequence_am/dummy_sequence_am.c
new file mode 100644
index 0000000000..b5ee5d89da
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/dummy_sequence_am.c
@@ -0,0 +1,110 @@
+/*-------------------------------------------------------------------------
+ *
+ * dummy_sequence_am.c
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/test/modules/dummy_sequence_am/dummy_sequence_am.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/sequenceam.h"
+#include "fmgr.h"
+
+PG_MODULE_MAGIC;
+
+/* this sequence is fully on-memory */
+static int	dummy_seqam_last_value = 1;
+static bool dummy_seqam_is_called = false;
+
+PG_FUNCTION_INFO_V1(dummy_sequenceam_handler);
+
+
+/* ------------------------------------------------------------------------
+ * Callbacks for the dummy 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 *
+dummy_sequenceam_get_table_am(void)
+{
+	return "heap";
+}
+
+static void
+dummy_sequenceam_init(Relation rel, int64 last_value, bool is_called)
+{
+	dummy_seqam_last_value = last_value;
+	dummy_seqam_is_called = is_called;
+}
+
+static int64
+dummy_sequenceam_nextval(Relation rel, int64 incby, int64 maxv,
+						 int64 minv, int64 cache, bool cycle,
+						 int64 *last)
+{
+	dummy_seqam_last_value += incby;
+	dummy_seqam_is_called = true;
+
+	return dummy_seqam_last_value;
+}
+
+static void
+dummy_sequenceam_setval(Relation rel, int64 next, bool iscalled)
+{
+	dummy_seqam_last_value = next;
+	dummy_seqam_is_called = iscalled;
+}
+
+static void
+dummy_sequenceam_get_state(Relation rel, int64 *last_value, bool *is_called)
+{
+	*last_value = dummy_seqam_last_value;
+	*is_called = dummy_seqam_is_called;
+}
+
+static void
+dummy_sequenceam_reset(Relation rel, int64 startv, bool is_called,
+					   bool reset_state)
+{
+	dummy_seqam_last_value = startv;
+	dummy_seqam_is_called = is_called;
+}
+
+static void
+dummy_sequenceam_change_persistence(Relation rel, char newrelpersistence)
+{
+	/* nothing to do, really */
+}
+
+/* ------------------------------------------------------------------------
+ * Definition of the dummy sequence access method.
+ * ------------------------------------------------------------------------
+ */
+
+static const SequenceAmRoutine dummy_sequenceam_methods = {
+	.type = T_SequenceAmRoutine,
+	.get_table_am = dummy_sequenceam_get_table_am,
+	.init = dummy_sequenceam_init,
+	.nextval = dummy_sequenceam_nextval,
+	.setval = dummy_sequenceam_setval,
+	.get_state = dummy_sequenceam_get_state,
+	.reset = dummy_sequenceam_reset,
+	.change_persistence = dummy_sequenceam_change_persistence
+};
+
+Datum
+dummy_sequenceam_handler(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_POINTER(&dummy_sequenceam_methods);
+}
diff --git a/src/test/modules/dummy_sequence_am/dummy_sequence_am.control b/src/test/modules/dummy_sequence_am/dummy_sequence_am.control
new file mode 100644
index 0000000000..9f10622f2f
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/dummy_sequence_am.control
@@ -0,0 +1,5 @@
+# dummy_sequence_am extension
+comment = 'dummy_sequence_am - sequence access method template'
+default_version = '1.0'
+module_pathname = '$libdir/dummy_sequence_am'
+relocatable = true
diff --git a/src/test/modules/dummy_sequence_am/expected/dummy_sequence.out b/src/test/modules/dummy_sequence_am/expected/dummy_sequence.out
new file mode 100644
index 0000000000..57588cea5b
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/expected/dummy_sequence.out
@@ -0,0 +1,35 @@
+CREATE EXTENSION dummy_sequence_am;
+CREATE SEQUENCE dummyseq USING dummy_sequence_am;
+SELECT nextval('dummyseq'::regclass);
+ nextval 
+---------
+       2
+(1 row)
+
+SELECT setval('dummyseq'::regclass, 14);
+ setval 
+--------
+     14
+(1 row)
+
+SELECT nextval('dummyseq'::regclass);
+ nextval 
+---------
+      15
+(1 row)
+
+-- Sequence relation exists, but it has no attributes.
+SELECT * FROM dummyseq;
+--
+(0 rows)
+
+-- Reset connection, which will reset the sequence
+\c
+SELECT nextval('dummyseq'::regclass);
+ nextval 
+---------
+       2
+(1 row)
+
+DROP SEQUENCE dummyseq;
+DROP EXTENSION dummy_sequence_am;
diff --git a/src/test/modules/dummy_sequence_am/meson.build b/src/test/modules/dummy_sequence_am/meson.build
new file mode 100644
index 0000000000..84460070e4
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+dummy_sequence_am_sources = files(
+  'dummy_sequence_am.c',
+)
+
+if host_system == 'windows'
+  dummy_sequence_am_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dummy_sequence_am',
+    '--FILEDESC', 'dummy_sequence_am - sequence access method template',])
+endif
+
+dummy_sequence_am = shared_module('dummy_sequence_am',
+  dummy_sequence_am_sources,
+  kwargs: pg_test_mod_args,
+)
+test_install_libs += dummy_sequence_am
+
+test_install_data += files(
+  'dummy_sequence_am.control',
+  'dummy_sequence_am--1.0.sql',
+)
+
+tests += {
+  'name': 'dummy_sequence_am',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'regress': {
+    'sql': [
+      'dummy_sequence',
+    ],
+  },
+}
diff --git a/src/test/modules/dummy_sequence_am/sql/dummy_sequence.sql b/src/test/modules/dummy_sequence_am/sql/dummy_sequence.sql
new file mode 100644
index 0000000000..c739b29a46
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/sql/dummy_sequence.sql
@@ -0,0 +1,17 @@
+CREATE EXTENSION dummy_sequence_am;
+
+CREATE SEQUENCE dummyseq USING dummy_sequence_am;
+
+SELECT nextval('dummyseq'::regclass);
+SELECT setval('dummyseq'::regclass, 14);
+SELECT nextval('dummyseq'::regclass);
+
+-- Sequence relation exists, but it has no attributes.
+SELECT * FROM dummyseq;
+
+-- Reset connection, which will reset the sequence
+\c
+SELECT nextval('dummyseq'::regclass);
+
+DROP SEQUENCE dummyseq;
+DROP EXTENSION dummy_sequence_am;
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index b76f588559..9a4c4ac506 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -5,6 +5,7 @@ subdir('commit_ts')
 subdir('delay_execution')
 subdir('dummy_index_am')
 subdir('dummy_seclabel')
+subdir('dummy_sequence_am')
 subdir('ldap_password_func')
 subdir('libpq_pipeline')
 subdir('plsample')
-- 
2.43.0

#2Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#1)
10 attachment(s)
Re: Sequence Access Methods, round two

On Fri, Dec 01, 2023 at 02:00:54PM +0900, Michael Paquier wrote:

- 0006 includes the backend changes, that caches a set of callback
routines for each sequence Relation, with an optional rd_tableam.
Callbacks are documented in sequenceam.h. Perhaps the sequence RMGR
renames should be split into a patch of its own, or just let as-is as
as it could be shared across more than one AM, but I did not see a
huge argument one way or another. The diffs are not that bad
considering that the original patch at +1200 lines for src/backend/,
with less documentation for the internal callbacks:
45 files changed, 1414 insertions(+), 718 deletions(-)

While looking at the patch set, I have noticed that the previous patch
0006 for the backend changes could be split into two patches to make
the review much easier, as of
- A first patch moving the code related to the in-core sequence AM
from commands/sequence.c to access/sequence/local.c, reshaping the
sequence RMGR:
12 files changed, 793 insertions(+), 611 deletions(-)
- A second patch to introduce the callbacks, the relcache and the
backend pieces, renaming the contents moved to local.c by the first
patch switching it to the handler:
38 files changed, 661 insertions(+), 155 deletions(-)

So please find attached a v2 set, with some typos fixed on top of this
extra split.

While on it, I have been doing some performance tests to see the
effect of the extra function pointers from the handler, required for
the computation of nextval(), using:
- Postgres on a tmpfs, running on scissors.
- An unlogged sequence.
- "SELECT count(nextval('popo')) FROM generate_series(1,N);" where N >
0.
At N=5M, one of my perf machines takes 3230ms in average to run the
query on HEAD (646ns per value, 20 runs), and 3315ms with the patch
(663ns, 20 runs), which is.. Err, not noticeable. But perhaps
somebody has a better idea of tests, say more micro-benchmarking
around nextval_internal()?
--
Michael

Attachments:

v2-0001-Switch-pg_sequence_last_value-to-report-a-tuple-a.patchtext/x-diff; charset=us-asciiDownload
From 940aadb33155b90843d6e07fedbe1c13b767c5ea Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 09:37:24 +0900
Subject: [PATCH v2 01/10] Switch pg_sequence_last_value() to report a tuple
 and use it in pg_dump

This commit switches pg_sequence_last_value() to report a tuple made of
(last_value,is_called) that can be directly be used for the arguments of
setval() in a sequence.

Going forward with PostgreSQL 17, pg_dump and pg_sequences make use of
it instead of scanning the heap table assumed to always exist for a
sequence.

Note: this requires a catversion bump.
---
 src/include/catalog/pg_proc.dat      |  6 ++++--
 src/backend/catalog/system_views.sql |  6 +++++-
 src/backend/commands/sequence.c      | 19 +++++++++++++------
 src/bin/pg_dump/pg_dump.c            | 16 +++++++++++++---
 src/test/regress/expected/rules.out  |  7 ++++++-
 5 files changed, 41 insertions(+), 13 deletions(-)

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 77e8b13764..33c995cd06 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3325,9 +3325,11 @@
   proargmodes => '{i,o,o,o,o,o,o,o}',
   proargnames => '{sequence_oid,start_value,minimum_value,maximum_value,increment,cycle_option,cache_size,data_type}',
   prosrc => 'pg_sequence_parameters' },
-{ oid => '4032', descr => 'sequence last value',
+{ oid => '4032', descr => 'sequence last value data',
   proname => 'pg_sequence_last_value', provolatile => 'v', proparallel => 'u',
-  prorettype => 'int8', proargtypes => 'regclass',
+  prorettype => 'record', proargtypes => 'regclass',
+  proallargtypes => '{regclass,bool,int8}', proargmodes => '{i,o,o}',
+  proargnames => '{seqname,is_called,last_value}',
   prosrc => 'pg_sequence_last_value' },
 
 { oid => '275', descr => 'return the next oid for a system table',
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 11d18ed9dd..009940dd80 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -178,7 +178,11 @@ CREATE VIEW pg_sequences AS
         S.seqcache AS cache_size,
         CASE
             WHEN has_sequence_privilege(C.oid, 'SELECT,USAGE'::text)
-                THEN pg_sequence_last_value(C.oid)
+                THEN (SELECT
+                          CASE WHEN sl.is_called
+                              THEN sl.last_value ELSE NULL
+                          END
+                      FROM pg_sequence_last_value(C.oid) sl)
             ELSE NULL
         END AS last_value
     FROM pg_sequence S JOIN pg_class C ON (C.oid = S.seqrelid)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index da2ace79cc..9c94255f24 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1779,14 +1779,22 @@ pg_sequence_parameters(PG_FUNCTION_ARGS)
 Datum
 pg_sequence_last_value(PG_FUNCTION_ARGS)
 {
+#define PG_SEQUENCE_LAST_VALUE_COLS		2
 	Oid			relid = PG_GETARG_OID(0);
+	Datum		values[PG_SEQUENCE_LAST_VALUE_COLS] = {0};
+	bool		nulls[PG_SEQUENCE_LAST_VALUE_COLS] = {0};
 	SeqTable	elm;
 	Relation	seqrel;
+	TupleDesc	tupdesc;
 	Buffer		buf;
 	HeapTupleData seqtuple;
 	Form_pg_sequence_data seq;
 	bool		is_called;
-	int64		result;
+	int64		last_value;
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	/* open and lock sequence */
 	init_sequence(relid, &elm, &seqrel);
@@ -1800,15 +1808,14 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	seq = read_seq_tuple(seqrel, &buf, &seqtuple);
 
 	is_called = seq->is_called;
-	result = seq->last_value;
+	last_value = seq->last_value;
 
 	UnlockReleaseBuffer(buf);
 	relation_close(seqrel, NoLock);
 
-	if (is_called)
-		PG_RETURN_INT64(result);
-	else
-		PG_RETURN_NULL();
+	values[0] = BoolGetDatum(is_called);
+	values[1] = Int64GetDatum(last_value);
+	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
 }
 
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8c0b5486b9..c7612c3793 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -17476,9 +17476,19 @@ dumpSequenceData(Archive *fout, const TableDataInfo *tdinfo)
 	bool		called;
 	PQExpBuffer query = createPQExpBuffer();
 
-	appendPQExpBuffer(query,
-					  "SELECT last_value, is_called FROM %s",
-					  fmtQualifiedDumpable(tbinfo));
+	/*
+	 * In versions 17 and up, pg_sequence_last_value() has been switched to
+	 * return a tuple with last_value and is_called.
+	 */
+	if (fout->remoteVersion >= 170000)
+		appendPQExpBuffer(query,
+						  "SELECT last_value, is_called "
+						  "FROM pg_sequence_last_value('%s')",
+						  fmtQualifiedDumpable(tbinfo));
+	else
+		appendPQExpBuffer(query,
+						  "SELECT last_value, is_called FROM %s",
+						  fmtQualifiedDumpable(tbinfo));
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 05070393b9..105e8f5eb4 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1696,7 +1696,12 @@ pg_sequences| SELECT n.nspname AS schemaname,
     s.seqcycle AS cycle,
     s.seqcache AS cache_size,
         CASE
-            WHEN has_sequence_privilege(c.oid, 'SELECT,USAGE'::text) THEN pg_sequence_last_value((c.oid)::regclass)
+            WHEN has_sequence_privilege(c.oid, 'SELECT,USAGE'::text) THEN ( SELECT
+                    CASE
+                        WHEN sl.is_called THEN sl.last_value
+                        ELSE NULL::bigint
+                    END AS "case"
+               FROM pg_sequence_last_value((c.oid)::regclass) sl(is_called, last_value))
             ELSE NULL::bigint
         END AS last_value
    FROM ((pg_sequence s
-- 
2.43.0

v2-0002-Introduce-sequence_-access-functions.patchtext/x-diff; charset=us-asciiDownload
From b5e7b31998252b2ae98a89e06636f878a6aac4d2 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 10:02:07 +0900
Subject: [PATCH v2 02/10] Introduce sequence_*() access functions

Similarly to tables and indexes, these functions are only able to open
relations with a sequence relkind, which is useful to make a distinction
with the other types that can have AMs.
---
 src/include/access/sequence.h           | 23 ++++++++
 src/backend/access/Makefile             |  2 +-
 src/backend/access/meson.build          |  1 +
 src/backend/access/sequence/Makefile    | 17 ++++++
 src/backend/access/sequence/meson.build |  5 ++
 src/backend/access/sequence/sequence.c  | 78 +++++++++++++++++++++++++
 src/backend/commands/sequence.c         | 31 +++++-----
 src/test/regress/expected/sequence.out  |  3 +-
 8 files changed, 140 insertions(+), 20 deletions(-)
 create mode 100644 src/include/access/sequence.h
 create mode 100644 src/backend/access/sequence/Makefile
 create mode 100644 src/backend/access/sequence/meson.build
 create mode 100644 src/backend/access/sequence/sequence.c

diff --git a/src/include/access/sequence.h b/src/include/access/sequence.h
new file mode 100644
index 0000000000..5d2a3d8a71
--- /dev/null
+++ b/src/include/access/sequence.h
@@ -0,0 +1,23 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequence.h
+ *	  Generic routines for sequence related code.
+ *
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/sequence/sequence.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ACCESS_SEQUENCE_H
+#define ACCESS_SEQUENCE_H
+
+#include "storage/lockdefs.h"
+#include "utils/relcache.h"
+
+extern Relation sequence_open(Oid relationId, LOCKMODE lockmode);
+extern void sequence_close(Relation relation, LOCKMODE lockmode);
+
+#endif							/* ACCESS_SEQUENCE_H */
diff --git a/src/backend/access/Makefile b/src/backend/access/Makefile
index 0880e0a8bb..1932d11d15 100644
--- a/src/backend/access/Makefile
+++ b/src/backend/access/Makefile
@@ -9,6 +9,6 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 SUBDIRS	    = brin common gin gist hash heap index nbtree rmgrdesc spgist \
-			  table tablesample transam
+			  sequence table tablesample transam
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/meson.build b/src/backend/access/meson.build
index aa99c4d162..ee08dcec9b 100644
--- a/src/backend/access/meson.build
+++ b/src/backend/access/meson.build
@@ -9,6 +9,7 @@ subdir('heap')
 subdir('index')
 subdir('nbtree')
 subdir('rmgrdesc')
+subdir('sequence')
 subdir('spgist')
 subdir('table')
 subdir('tablesample')
diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile
new file mode 100644
index 0000000000..9f9d31f542
--- /dev/null
+++ b/src/backend/access/sequence/Makefile
@@ -0,0 +1,17 @@
+#-------------------------------------------------------------------------
+#
+# Makefile
+#    Makefile for access/sequence
+#
+# IDENTIFICATION
+#    src/backend/access/sequence/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/access/sequence
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = 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
new file mode 100644
index 0000000000..1840a913bc
--- /dev/null
+++ b/src/backend/access/sequence/meson.build
@@ -0,0 +1,5 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+backend_sources += files(
+  'sequence.c',
+)
diff --git a/src/backend/access/sequence/sequence.c b/src/backend/access/sequence/sequence.c
new file mode 100644
index 0000000000..a5b9fccb50
--- /dev/null
+++ b/src/backend/access/sequence/sequence.c
@@ -0,0 +1,78 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequence.c
+ *	  Generic routines for sequence related code.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/sequence/sequence.c
+ *
+ *
+ * NOTES
+ *	  This file contains sequence_ routines that implement access to sequences
+ *	  (in contrast to other relation types like indexes).
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/relation.h"
+#include "access/sequence.h"
+#include "storage/lmgr.h"
+
+static inline void validate_relation_kind(Relation r);
+
+/* ----------------
+ *		sequence_open - open a sequence relation by relation OID
+ *
+ *		This is essentially relation_open plus check that the relation
+ *		is a sequence.
+ * ----------------
+ */
+Relation
+sequence_open(Oid relationId, LOCKMODE lockmode)
+{
+	Relation	r;
+
+	r = relation_open(relationId, lockmode);
+
+	validate_relation_kind(r);
+
+	return r;
+}
+
+/* ----------------
+ *		sequence_close - close a sequence
+ *
+ *		If lockmode is not "NoLock", we then release the specified lock.
+ *
+ *		Note that it is often sensible to hold a lock beyond relation_close;
+ *		in that case, the lock is released automatically at xact end.
+ *		----------------
+ */
+void
+sequence_close(Relation relation, LOCKMODE lockmode)
+{
+	relation_close(relation, lockmode);
+}
+
+/* ----------------
+ *		validate_relation_kind - check the relation's kind
+ *
+ *		Make sure relkind is from an index
+ * ----------------
+ */
+static inline void
+validate_relation_kind(Relation r)
+{
+	if (r->rd_rel->relkind != RELKIND_SEQUENCE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot open relation \"%s\"",
+						RelationGetRelationName(r)),
+				 errdetail_relkind_not_supported(r->rd_rel->relkind)));
+}
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 9c94255f24..117518d480 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -18,6 +18,7 @@
 #include "access/htup_details.h"
 #include "access/multixact.h"
 #include "access/relation.h"
+#include "access/sequence.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -208,7 +209,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	seqoid = address.objectId;
 	Assert(seqoid != InvalidOid);
 
-	rel = table_open(seqoid, AccessExclusiveLock);
+	rel = sequence_open(seqoid, AccessExclusiveLock);
 	tupDesc = RelationGetDescr(rel);
 
 	/* now initialize the sequence's data */
@@ -219,7 +220,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	if (owned_by)
 		process_owned_by(rel, owned_by, seq->for_identity);
 
-	table_close(rel, NoLock);
+	sequence_close(rel, NoLock);
 
 	/* fill in pg_sequence */
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
@@ -324,7 +325,7 @@ ResetSequence(Oid seq_relid)
 	/* Note that we do not change the currval() state */
 	elm->cached = elm->last;
 
-	relation_close(seq_rel, NoLock);
+	sequence_close(seq_rel, NoLock);
 }
 
 /*
@@ -531,7 +532,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	ObjectAddressSet(address, RelationRelationId, relid);
 
 	table_close(rel, RowExclusiveLock);
-	relation_close(seqrel, NoLock);
+	sequence_close(seqrel, NoLock);
 
 	return address;
 }
@@ -555,7 +556,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence)
 	fill_seq_with_data(seqrel, &seqdatatuple);
 	UnlockReleaseBuffer(buf);
 
-	relation_close(seqrel, NoLock);
+	sequence_close(seqrel, NoLock);
 }
 
 void
@@ -662,7 +663,7 @@ nextval_internal(Oid relid, bool check_permissions)
 		Assert(elm->last_valid);
 		Assert(elm->increment != 0);
 		elm->last += elm->increment;
-		relation_close(seqrel, NoLock);
+		sequence_close(seqrel, NoLock);
 		last_used_seq = elm;
 		return elm->last;
 	}
@@ -849,7 +850,7 @@ nextval_internal(Oid relid, bool check_permissions)
 
 	UnlockReleaseBuffer(buf);
 
-	relation_close(seqrel, NoLock);
+	sequence_close(seqrel, NoLock);
 
 	return result;
 }
@@ -880,7 +881,7 @@ currval_oid(PG_FUNCTION_ARGS)
 
 	result = elm->last;
 
-	relation_close(seqrel, NoLock);
+	sequence_close(seqrel, NoLock);
 
 	PG_RETURN_INT64(result);
 }
@@ -915,7 +916,7 @@ lastval(PG_FUNCTION_ARGS)
 						RelationGetRelationName(seqrel))));
 
 	result = last_used_seq->last;
-	relation_close(seqrel, NoLock);
+	sequence_close(seqrel, NoLock);
 
 	PG_RETURN_INT64(result);
 }
@@ -1030,7 +1031,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 
 	UnlockReleaseBuffer(buf);
 
-	relation_close(seqrel, NoLock);
+	sequence_close(seqrel, NoLock);
 }
 
 /*
@@ -1095,7 +1096,7 @@ lock_and_open_sequence(SeqTable seq)
 	}
 
 	/* We now know we have the lock, and can safely open the rel */
-	return relation_open(seq->relid, NoLock);
+	return sequence_open(seq->relid, NoLock);
 }
 
 /*
@@ -1152,12 +1153,6 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	 */
 	seqrel = lock_and_open_sequence(elm);
 
-	if (seqrel->rd_rel->relkind != RELKIND_SEQUENCE)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a sequence",
-						RelationGetRelationName(seqrel))));
-
 	/*
 	 * If the sequence has been transactionally replaced since we last saw it,
 	 * discard any cached-but-unissued values.  We do not touch the currval()
@@ -1811,7 +1806,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	last_value = seq->last_value;
 
 	UnlockReleaseBuffer(buf);
-	relation_close(seqrel, NoLock);
+	sequence_close(seqrel, NoLock);
 
 	values[0] = BoolGetDatum(is_called);
 	values[1] = Int64GetDatum(last_value);
diff --git a/src/test/regress/expected/sequence.out b/src/test/regress/expected/sequence.out
index 7cb2f7cc02..2b47b7796b 100644
--- a/src/test/regress/expected/sequence.out
+++ b/src/test/regress/expected/sequence.out
@@ -313,7 +313,8 @@ ALTER SEQUENCE IF EXISTS sequence_test2 RESTART WITH 24
   INCREMENT BY 4 MAXVALUE 36 MINVALUE 5 CYCLE;
 NOTICE:  relation "sequence_test2" does not exist, skipping
 ALTER SEQUENCE serialTest1 CYCLE;  -- error, not a sequence
-ERROR:  "serialtest1" is not a sequence
+ERROR:  cannot open relation "serialtest1"
+DETAIL:  This operation is not supported for tables.
 CREATE SEQUENCE sequence_test2 START WITH 32;
 CREATE SEQUENCE sequence_test4 INCREMENT BY -1;
 SELECT nextval('sequence_test2');
-- 
2.43.0

v2-0003-Group-more-closely-local-sequence-cache-updates.patchtext/x-diff; charset=us-asciiDownload
From ba986c43be4f08c259d1411c6f6d27f399fc1fda Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 10:08:17 +0900
Subject: [PATCH v2 03/10] Group more closely local sequence cache updates

Previously, some updates of the informations for SeqTable entries was
mixed in the middle of computations.  Grouping them makes the code
easier to follow and split later on.
---
 src/backend/commands/sequence.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 117518d480..ef71efdb82 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -489,10 +489,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 				seqform, newdataform,
 				&need_seq_rewrite, &owned_by);
 
-	/* Clear local cache so that we don't think we have cached numbers */
-	/* Note that we do not change the currval() state */
-	elm->cached = elm->last;
-
 	/* If needed, rewrite the sequence relation itself */
 	if (need_seq_rewrite)
 	{
@@ -520,6 +516,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		fill_seq_with_data(seqrel, newdatatuple);
 	}
 
+	/* Clear local cache so that we don't think we have cached numbers */
+	/* Note that we do not change the currval() state */
+	elm->cached = elm->last;
+
 	/* process OWNED BY if given */
 	if (owned_by)
 		process_owned_by(seqrel, owned_by, stmt->for_identity);
@@ -683,7 +683,6 @@ nextval_internal(Oid relid, bool check_permissions)
 	seq = read_seq_tuple(seqrel, &buf, &seqdatatuple);
 	page = BufferGetPage(buf);
 
-	elm->increment = incby;
 	last = next = result = seq->last_value;
 	fetch = cache;
 	log = seq->log_cnt;
@@ -781,6 +780,7 @@ nextval_internal(Oid relid, bool check_permissions)
 	Assert(log >= 0);
 
 	/* save info in local cache */
+	elm->increment = incby;
 	elm->last = result;			/* last returned number */
 	elm->cached = last;			/* last fetched number */
 	elm->last_valid = true;
-- 
2.43.0

v2-0004-Remove-FormData_pg_sequence_data-from-init_params.patchtext/x-diff; charset=us-asciiDownload
From f9e8dc91c2defc81d6b69f512b7994d3c9d0c4a4 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:00:45 +0900
Subject: [PATCH v2 04/10] Remove FormData_pg_sequence_data from
 init_params()/sequence.c

init_params() sets up "last_value" and "is_called" for a sequence, based
on the sequence properties in pg_sequences.  This simplifies the logic
around log_cnt, which is reset to 0 when the metadata of a sequence is
expected to start from afresh when its properties are updated.
---
 src/backend/commands/sequence.c | 81 ++++++++++++++++++++-------------
 1 file changed, 49 insertions(+), 32 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index ef71efdb82..10c96a266f 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -106,7 +106,9 @@ static Form_pg_sequence_data read_seq_tuple(Relation rel,
 static void init_params(ParseState *pstate, List *options, bool for_identity,
 						bool isInit,
 						Form_pg_sequence seqform,
-						Form_pg_sequence_data seqdataform,
+						int64 *last_value,
+						bool *reset_state,
+						bool *is_called,
 						bool *need_seq_rewrite,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
@@ -121,7 +123,9 @@ ObjectAddress
 DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 {
 	FormData_pg_sequence seqform;
-	FormData_pg_sequence_data seqdataform;
+	int64		last_value;
+	bool		reset_state;
+	bool		is_called;
 	bool		need_seq_rewrite;
 	List	   *owned_by;
 	CreateStmt *stmt = makeNode(CreateStmt);
@@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* Check and set all option values */
 	init_params(pstate, seq->options, seq->for_identity, true,
-				&seqform, &seqdataform,
+				&seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/*
@@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 		{
 			case SEQ_COL_LASTVAL:
 				coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
-				value[i - 1] = Int64GetDatumFast(seqdataform.last_value);
+				value[i - 1] = Int64GetDatumFast(last_value);
 				break;
 			case SEQ_COL_LOG:
 				coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
@@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	ObjectAddress address;
 	Relation	rel;
 	HeapTuple	seqtuple;
+	bool		reset_state = false;
+	bool		is_called;
+	int64		last_value;
 	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
@@ -481,12 +488,14 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	/* 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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
-				seqform, newdataform,
+				seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/* If needed, rewrite the sequence relation itself */
@@ -513,6 +522,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * 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);
 	}
 
@@ -1229,17 +1242,19 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 /*
  * init_params: process the options list of CREATE or ALTER SEQUENCE, and
  * store the values into appropriate fields of seqform, for changes that go
- * into the pg_sequence catalog, and fields of seqdataform for changes to the
- * sequence relation itself.  Set *need_seq_rewrite to true if we changed any
- * parameters that require rewriting the sequence's relation (interesting for
- * ALTER SEQUENCE).  Also set *owned_by to any OWNED BY option, or to NIL if
- * there is none.
+ * into the pg_sequence catalog, and fields for changes to the sequence
+ * relation itself (is_called, last_value or any state it may hold).  Set
+ * *need_seq_rewrite to true if we changed any parameters that require
+ * rewriting the sequence's relation (interesting for ALTER SEQUENCE).  Also
+ * set *owned_by to any OWNED BY option, or to NIL if there is none.  Set
+ * *reset_state if the internal state of the sequence needs to change on a
+ * follow-up nextval().
  *
  * If isInit is true, fill any unspecified options with default values;
  * otherwise, do not change existing options that aren't explicitly overridden.
  *
  * Note: we force a sequence rewrite whenever we change parameters that affect
- * generation of future sequence values, even if the seqdataform per se is not
+ * generation of future sequence values, even if the metadata per se is not
  * changed.  This allows ALTER SEQUENCE to behave transactionally.  Currently,
  * the only option that doesn't cause that is OWNED BY.  It's *necessary* for
  * ALTER SEQUENCE OWNED BY to not rewrite the sequence, because that would
@@ -1250,7 +1265,9 @@ static void
 init_params(ParseState *pstate, List *options, bool for_identity,
 			bool isInit,
 			Form_pg_sequence seqform,
-			Form_pg_sequence_data seqdataform,
+			int64 *last_value,
+			bool *reset_state,
+			bool *is_called,
 			bool *need_seq_rewrite,
 			List **owned_by)
 {
@@ -1353,11 +1370,11 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	}
 
 	/*
-	 * We must reset log_cnt when isInit or when changing any parameters that
-	 * would affect future nextval allocations.
+	 * We must reset the state when isInit or when changing any parameters
+	 * that would affect future nextval allocations.
 	 */
 	if (isInit)
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 
 	/* AS type */
 	if (as_type != NULL)
@@ -1406,7 +1423,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("INCREMENT must not be zero")));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1418,7 +1435,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	{
 		seqform->seqcycle = boolVal(is_cycled->arg);
 		Assert(BoolIsValid(seqform->seqcycle));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1429,7 +1446,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (max_value != NULL && max_value->arg)
 	{
 		seqform->seqmax = defGetInt64(max_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || max_value != NULL || reset_max_value)
 	{
@@ -1445,7 +1462,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmax = -1;	/* descending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate maximum value.  No need to check INT8 as seqmax is an int64 */
@@ -1461,7 +1478,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (min_value != NULL && min_value->arg)
 	{
 		seqform->seqmin = defGetInt64(min_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || min_value != NULL || reset_min_value)
 	{
@@ -1477,7 +1494,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmin = 1;	/* ascending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate minimum value.  No need to check INT8 as seqmin is an int64 */
@@ -1528,30 +1545,30 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (restart_value != NULL)
 	{
 		if (restart_value->arg != NULL)
-			seqdataform->last_value = defGetInt64(restart_value);
+			*last_value = defGetInt64(restart_value);
 		else
-			seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
-		seqdataform->log_cnt = 0;
+			*last_value = seqform->seqstart;
+		*is_called = false;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-		seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
+		*last_value = seqform->seqstart;
+		*is_called = false;
 	}
 
 	/* crosscheck RESTART (or current value, if changing MIN/MAX) */
-	if (seqdataform->last_value < seqform->seqmin)
+	if (*last_value < seqform->seqmin)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be less than MINVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmin)));
-	if (seqdataform->last_value > seqform->seqmax)
+	if (*last_value > seqform->seqmax)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be greater than MAXVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmax)));
 
 	/* CACHE */
@@ -1563,7 +1580,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("CACHE (%lld) must be greater than zero",
 							(long long) seqform->seqcache)));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-- 
2.43.0

v2-0005-Integrate-addition-of-attributes-for-sequences-wi.patchtext/x-diff; charset=us-asciiDownload
From 4ffafa319fd938be33a4fc3632e3db76c89ebeae Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:16:13 +0900
Subject: [PATCH v2 05/10] 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               | 29 +++++++++++++++++--
 src/backend/commands/tablecmds.c              | 10 +++++++
 src/backend/tcop/utility.c                    |  4 +++
 .../test_ddl_deparse/expected/alter_table.out | 10 +++++--
 .../expected/create_sequence_1.out            |  5 +++-
 .../expected/create_table.out                 | 15 ++++++++--
 .../test_ddl_deparse/test_ddl_deparse.c       |  3 ++
 8 files changed, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e494309da8..6947225b64 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2186,6 +2186,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 10c96a266f..d71e195c83 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -136,6 +136,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;
@@ -174,7 +177,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;
@@ -198,7 +200,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;
@@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 6b0a20010e..847468705c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4488,6 +4488,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 */
@@ -4782,6 +4783,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,
@@ -5195,6 +5203,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,
@@ -6347,6 +6356,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 366a27ae8e..64b381d97e 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1671,7 +1671,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 ecde9d7422..3332954c97 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
@@ -71,7 +74,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 desc column a of table parent
diff --git a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
index 5837ea484e..310ce5a6ba 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.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 75b62aff4d..69e54358ee 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 alter table, tag ALTER TABLE
 NOTICE:    subcommand: type SET ATTNOTNULL desc <NULL>
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 0302f79bb7..ec07c173b8 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -114,6 +114,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.43.0

v2-0006-Move-code-for-local-sequences-to-own-file.patchtext/x-diff; charset=us-asciiDownload
From 5183081e29f9f5a2e84e25c0fcf28197f12f12cd Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 4 Dec 2023 15:35:44 +0900
Subject: [PATCH v2 06/10] Move code for local sequences to own file

Now that the separation between the in-core sequence computations and
the catalog layer is clean, this moves the code corresponding to the
"local" sequence AM into its own file, out of sequence.c.  The WAL
routines related to sequence are moved in it as well.
---
 src/include/access/localam.h                  |  48 ++
 src/include/access/rmgrlist.h                 |   2 +-
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 .../rmgrdesc/{seqdesc.c => localseqdesc.c}    |  18 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/local.c           | 706 ++++++++++++++++++
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 619 +--------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 12 files changed, 793 insertions(+), 611 deletions(-)
 create mode 100644 src/include/access/localam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => localseqdesc.c} (69%)
 create mode 100644 src/backend/access/sequence/local.c

diff --git a/src/include/access/localam.h b/src/include/access/localam.h
new file mode 100644
index 0000000000..5b0575dc2e
--- /dev/null
+++ b/src/include/access/localam.h
@@ -0,0 +1,48 @@
+/*-------------------------------------------------------------------------
+ *
+ * localam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/localam.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef LOCALAM_H
+#define LOCALAM_H
+
+#include "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_LOCAL_SEQ_LOG			0x00
+
+typedef struct xl_local_seq_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_local_seq_rec;
+
+extern void local_seq_redo(XLogReaderState *record);
+extern void local_seq_desc(StringInfo buf, XLogReaderState *record);
+extern const char *local_seq_identify(uint8 info);
+extern void local_seq_mask(char *page, BlockNumber blkno);
+
+/* access routines */
+extern int64 local_seq_nextval(Relation rel, int64 incby, int64 maxv,
+							   int64 minv, int64 cache, bool cycle,
+							   int64 *last);
+extern const char *local_seq_get_table_am(void);
+extern void local_seq_init(Relation rel, int64 last_value, bool is_called);
+extern void local_seq_setval(Relation rel, int64 next, bool iscalled);
+extern void local_seq_reset(Relation rel, int64 startv, bool is_called,
+							bool reset_state);
+extern void local_seq_get_state(Relation rel, int64 *last_value,
+								bool *is_called);
+extern void local_seq_change_persistence(Relation rel,
+										 char newrelpersistence);
+
+#endif							/* LOCALAM_H */
diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 463bcb67c5..544997b01d 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_LOCAL_SEQ_ID, "LocalSequence", local_seq_redo, local_seq_desc, local_seq_identify, NULL, NULL, local_seq_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/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile
index cd95eec37f..dff5a60e68 100644
--- a/src/backend/access/rmgrdesc/Makefile
+++ b/src/backend/access/rmgrdesc/Makefile
@@ -18,13 +18,13 @@ OBJS = \
 	gistdesc.o \
 	hashdesc.o \
 	heapdesc.o \
+	localseqdesc.o \
 	logicalmsgdesc.o \
 	mxactdesc.o \
 	nbtdesc.o \
 	relmapdesc.o \
 	replorigindesc.o \
 	rmgrdesc_utils.o \
-	seqdesc.o \
 	smgrdesc.o \
 	spgdesc.o \
 	standbydesc.o \
diff --git a/src/backend/access/rmgrdesc/seqdesc.c b/src/backend/access/rmgrdesc/localseqdesc.c
similarity index 69%
rename from src/backend/access/rmgrdesc/seqdesc.c
rename to src/backend/access/rmgrdesc/localseqdesc.c
index ba60544085..3e8dfda01f 100644
--- a/src/backend/access/rmgrdesc/seqdesc.c
+++ b/src/backend/access/rmgrdesc/localseqdesc.c
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
- * seqdesc.c
- *	  rmgr descriptor routines for commands/sequence.c
+ * localseqdesc.c
+ *	  rmgr descriptor routines for sequence/local.c
  *
  * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -14,31 +14,31 @@
  */
 #include "postgres.h"
 
-#include "commands/sequence.h"
+#include "access/localam.h"
 
 
 void
-seq_desc(StringInfo buf, XLogReaderState *record)
+local_seq_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_local_seq_rec *xlrec = (xl_local_seq_rec *) rec;
 
-	if (info == XLOG_SEQ_LOG)
+	if (info == XLOG_LOCAL_SEQ_LOG)
 		appendStringInfo(buf, "rel %u/%u/%u",
 						 xlrec->locator.spcOid, xlrec->locator.dbOid,
 						 xlrec->locator.relNumber);
 }
 
 const char *
-seq_identify(uint8 info)
+local_seq_identify(uint8 info)
 {
 	const char *id = NULL;
 
 	switch (info & ~XLR_INFO_MASK)
 	{
-		case XLOG_SEQ_LOG:
-			id = "LOG";
+		case XLOG_LOCAL_SEQ_LOG:
+			id = "LOCAL_SEQ_LOG";
 			break;
 	}
 
diff --git a/src/backend/access/rmgrdesc/meson.build b/src/backend/access/rmgrdesc/meson.build
index f76e87e2d7..46fbf12730 100644
--- a/src/backend/access/rmgrdesc/meson.build
+++ b/src/backend/access/rmgrdesc/meson.build
@@ -11,13 +11,13 @@ rmgr_desc_sources = files(
   'gistdesc.c',
   'hashdesc.c',
   'heapdesc.c',
+  'localseqdesc.c',
   'logicalmsgdesc.c',
   'mxactdesc.c',
   'nbtdesc.c',
   'relmapdesc.c',
   'replorigindesc.c',
   'rmgrdesc_utils.c',
-  'seqdesc.c',
   'smgrdesc.c',
   'spgdesc.c',
   'standbydesc.c',
diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile
index 9f9d31f542..697f89905e 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = local.o sequence.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/sequence/local.c b/src/backend/access/sequence/local.c
new file mode 100644
index 0000000000..e77f25e13e
--- /dev/null
+++ b/src/backend/access/sequence/local.c
@@ -0,0 +1,706 @@
+/*-------------------------------------------------------------------------
+ *
+ * local.c
+ *	  Local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/access/sequence/local.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/localam.h"
+#include "access/multixact.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"
+
+
+/*
+ * 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 LOCAL_SEQ_LOG_VALS	32
+
+/*
+ * The "special area" of a local sequence's buffer page looks like this.
+ */
+#define LOCAL_SEQ_MAGIC	  0x1717
+
+typedef struct local_sequence_magic
+{
+	uint32		magic;
+} local_sequence_magic;
+
+/* Format of tuples stored in heap table associated to local sequences */
+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 local 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
+
+
+/*
+ * 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
+
+static Form_pg_sequence_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_sequence_data
+read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
+{
+	Page		page;
+	ItemId		lp;
+	local_sequence_magic *sm;
+	Form_pg_sequence_data seq;
+
+	*buf = ReadBuffer(rel, 0);
+	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(*buf);
+	sm = (local_sequence_magic *) PageGetSpecialPointer(page);
+
+	if (sm->magic != LOCAL_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;
+}
+
+/*
+ * 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, InvalidBackendId);
+		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;
+	local_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(local_sequence_magic));
+	sm = (local_sequence_magic *) PageGetSpecialPointer(page);
+	sm->magic = LOCAL_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, (Item) 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_local_seq_rec xlrec;
+		XLogRecPtr	recptr;
+
+		XLogBeginInsert();
+		XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
+
+		xlrec.locator = rel->rd_locator;
+
+		XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec));
+		XLogRegisterData((char *) tuple->t_data, tuple->t_len);
+
+		recptr = XLogInsert(RM_LOCAL_SEQ_ID, XLOG_LOCAL_SEQ_LOG);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buf);
+}
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+local_seq_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+local_seq_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_local_seq_rec *xlrec = (xl_local_seq_rec *) XLogRecGetData(record);
+	local_sequence_magic *sm;
+
+	if (info != XLOG_LOCAL_SEQ_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = (Page) BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	PageInit(localpage, BufferGetPageSize(buffer), sizeof(local_sequence_magic));
+	sm = (local_sequence_magic *) PageGetSpecialPointer(localpage);
+	sm->magic = LOCAL_SEQ_MAGIC;
+
+	item = (char *) xlrec + sizeof(xl_local_seq_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_local_seq_rec);
+
+	if (PageAddItem(localpage, (Item) item, itemsz,
+					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "local_seq_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
+
+/*
+ * local_seq_nextval()
+ *
+ * Allocate a new value for a local sequence, based on the sequence
+ * configuration.
+ */
+int64
+local_seq_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_sequence_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_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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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_local_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 = rel->rd_locator;
+
+		XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec));
+		XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len);
+
+		recptr = XLogInsert(RM_LOCAL_SEQ_ID, XLOG_LOCAL_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);
+
+	return result;
+}
+
+/*
+ * local_seq_get_table_am()
+ *
+ * Return the table access method used by this sequence.
+ */
+const char *
+local_seq_get_table_am(void)
+{
+	return "heap";
+}
+
+/*
+ * local_seq_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
+local_seq_init(Relation rel, int64 last_value, bool is_called)
+{
+	Datum		value[SEQ_COL_LASTCOL];
+	bool		null[SEQ_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_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(0);
+				break;
+			case SEQ_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);
+}
+
+/*
+ * local_seq_setval()
+ *
+ * Callback for setval().
+ */
+void
+local_seq_setval(Relation rel, int64 next, bool iscalled)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_sequence_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_local_seq_rec xlrec;
+		XLogRecPtr	recptr;
+		Page		page = BufferGetPage(buf);
+
+		XLogBeginInsert();
+		XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
+
+		xlrec.locator = rel->rd_locator;
+		XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec));
+		XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len);
+
+		recptr = XLogInsert(RM_LOCAL_SEQ_ID, XLOG_LOCAL_SEQ_LOG);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buf);
+}
+
+/*
+ * local_seq_reset()
+ *
+ * Perform a hard reset on the local sequence, rewriting its heap data
+ * entirely.
+ */
+void
+local_seq_reset(Relation rel, int64 startv, bool is_called, bool reset_state)
+{
+	Form_pg_sequence_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_sequence_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);
+}
+
+/*
+ * local_seq_get_state()
+ *
+ * Retrieve the state of a local sequence.
+ */
+void
+local_seq_get_state(Relation rel, int64 *last_value, bool *is_called)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_sequence_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	*last_value = seq->last_value;
+	*is_called = seq->is_called;
+
+	UnlockReleaseBuffer(buf);
+}
+
+/*
+ * local_seq_change_persistence()
+ *
+ * Persistence change for the local sequence Relation.
+ */
+void
+local_seq_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/sequence/meson.build b/src/backend/access/sequence/meson.build
index 1840a913bc..b271956232 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,6 @@
 # Copyright (c) 2022-2023, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'local.c',
   'sequence.c',
 )
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 7d67eda5f7..c3f9acb064 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -15,6 +15,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/localam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index d71e195c83..f1b0c484e2 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/localam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -50,23 +51,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -96,13 +80,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,
@@ -134,14 +114,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
@@ -174,35 +148,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;
@@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	/* now initialize the sequence table structure and its data */
+	local_seq_init(rel, last_value, is_called);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -292,10 +222,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;
@@ -306,7 +232,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))
@@ -315,40 +240,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. */
+	local_seq_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 */
@@ -357,106 +250,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, InvalidBackendId);
-		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, (Item) 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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) tuple->t_data, tuple->t_len);
-
-		recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	UnlockReleaseBuffer(buf);
-}
-
 /*
  * AlterSequence
  *
@@ -468,10 +261,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;
@@ -480,7 +270,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	bool		reset_state = false;
 	bool		is_called;
 	int64		last_value;
-	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
 	relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -507,16 +296,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 */
+	local_seq_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -526,32 +307,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);
+		local_seq_reset(seqrel, last_value, is_called, reset_state);
 	}
 
 	/* Clear local cache so that we don't think we have cached numbers */
@@ -580,8 +339,6 @@ SequenceChangePersistence(Oid relid, char newrelpersistence)
 {
 	SeqTable	elm;
 	Relation	seqrel;
-	Buffer		buf;
-	HeapTupleData seqdatatuple;
 
 	init_sequence(relid, &elm, &seqrel);
 
@@ -589,10 +346,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);
+	local_seq_change_persistence(seqrel, newrelpersistence);
 
 	sequence_close(seqrel, NoLock);
 }
@@ -655,24 +409,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);
@@ -717,105 +462,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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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 = local_seq_nextval(seqrel, incby, maxv, minv, cache, cycle,
+							   &last);
 
 	/* save info in local cache */
 	elm->increment = incby;
@@ -825,69 +474,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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) 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;
@@ -977,9 +563,6 @@ do_setval(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,
@@ -1013,9 +596,6 @@ do_setval(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),
@@ -1037,37 +617,8 @@ do_setval(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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) 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 */
+	local_seq_setval(seqrel, next, iscalled);
 
 	sequence_close(seqrel, NoLock);
 }
@@ -1208,62 +759,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
@@ -1823,9 +1318,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	SeqTable	elm;
 	Relation	seqrel;
 	TupleDesc	tupdesc;
-	Buffer		buf;
-	HeapTupleData seqtuple;
-	Form_pg_sequence_data seq;
 	bool		is_called;
 	int64		last_value;
 
@@ -1842,12 +1334,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 				 errmsg("permission denied for sequence %s",
 						RelationGetRelationName(seqrel))));
 
-	seq = read_seq_tuple(seqrel, &buf, &seqtuple);
-
-	is_called = seq->is_called;
-	last_value = seq->last_value;
-
-	UnlockReleaseBuffer(buf);
+	local_seq_get_state(seqrel, &last_value, &is_called);
 	sequence_close(seqrel, NoLock);
 
 	values[0] = BoolGetDatum(is_called);
@@ -1855,57 +1342,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
 }
 
-
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = (Page) BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, (Item) item, itemsz,
-					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1920,14 +1356,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c76..0f45509f2c 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/localseqdesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index 6b8c17bb4c..ff09335607 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/localam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
-- 
2.43.0

v2-0007-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From 6b8991043c9d8b7734daf1a0106358850ca77ebc Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 4 Dec 2023 15:43:14 +0900
Subject: [PATCH v2 07/10] Sequence access methods - backend support

The "local" sequence AM is now plugged in as a handler in the relcache,
and a set of callbacks in sequenceam.h.
---
 src/include/access/localam.h                  |  15 --
 src/include/access/sequenceam.h               | 188 ++++++++++++++++++
 src/include/catalog/pg_am.dat                 |   3 +
 src/include/catalog/pg_am.h                   |   1 +
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/local.c           |  69 ++++---
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/sequence.c        |   3 +-
 src/backend/access/sequence/sequenceamapi.c   | 145 ++++++++++++++
 src/backend/catalog/heap.c                    |   3 +-
 src/backend/commands/amcmds.c                 |  16 ++
 src/backend/commands/sequence.c               |  21 +-
 src/backend/commands/tablecmds.c              |  12 +-
 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            |  87 ++++++--
 src/backend/utils/misc/guc_tables.c           |  12 ++
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 src/bin/psql/describe.c                       |   2 +
 src/bin/psql/tab-complete.c                   |   4 +-
 src/test/regress/expected/create_am.out       |  33 ++-
 src/test/regress/expected/opr_sanity.out      |  12 ++
 src/test/regress/expected/psql.out            |  64 +++---
 src/test/regress/sql/create_am.sql            |  24 ++-
 src/test/regress/sql/opr_sanity.sql           |  10 +
 src/tools/pgindent/typedefs.list              |   5 +-
 38 files changed, 661 insertions(+), 155 deletions(-)
 create mode 100644 src/include/access/sequenceam.h
 create mode 100644 src/backend/access/sequence/sequenceamapi.c

diff --git a/src/include/access/localam.h b/src/include/access/localam.h
index 5b0575dc2e..7afc0a9636 100644
--- a/src/include/access/localam.h
+++ b/src/include/access/localam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_LOCAL_SEQ_LOG			0x00
@@ -31,18 +30,4 @@ extern void local_seq_desc(StringInfo buf, XLogReaderState *record);
 extern const char *local_seq_identify(uint8 info);
 extern void local_seq_mask(char *page, BlockNumber blkno);
 
-/* access routines */
-extern int64 local_seq_nextval(Relation rel, int64 incby, int64 maxv,
-							   int64 minv, int64 cache, bool cycle,
-							   int64 *last);
-extern const char *local_seq_get_table_am(void);
-extern void local_seq_init(Relation rel, int64 last_value, bool is_called);
-extern void local_seq_setval(Relation rel, int64 next, bool iscalled);
-extern void local_seq_reset(Relation rel, int64 startv, bool is_called,
-							bool reset_state);
-extern void local_seq_get_state(Relation rel, int64 *last_value,
-								bool *is_called);
-extern void local_seq_change_persistence(Relation rel,
-										 char newrelpersistence);
-
 #endif							/* LOCALAM_H */
diff --git a/src/include/access/sequenceam.h b/src/include/access/sequenceam.h
new file mode 100644
index 0000000000..f0f36c6c7e
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,188 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2023, 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 "local"
+
+/* 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.
+	 */
+	void		(*get_state) (Relation rel, int64 *last_value, bool *is_called);
+
+	/*
+	 * 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)
+{
+	rel->rd_sequenceam->get_state(rel, last_value, is_called);
+}
+
+/*
+ * 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);
+
+/* ----------------------------------------------------------------------------
+ * Functions in local.c
+ * ----------------------------------------------------------------------------
+ */
+
+extern const SequenceAmRoutine *GetLocalSequenceAmRoutine(void);
+
+#endif							/* SEQUENCEAM_H */
diff --git a/src/include/catalog/pg_am.dat b/src/include/catalog/pg_am.dat
index ed641037dd..6de9f0f23d 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 => '8047', oid_symbol => 'LOCAL_SEQUENCE_AM_OID',
+  descr => 'local sequence access method',
+  amname => 'local', amhandler => '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 d5314bb38b..2d10b9c18a 100644
--- a/src/include/catalog/pg_am.h
+++ b/src/include/catalog/pg_am.h
@@ -56,6 +56,7 @@ DECLARE_UNIQUE_INDEX_PKEY(pg_am_oid_index, 2652, AmOidIndexId, pg_am, btree(oid
  * 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_proc.dat b/src/include/catalog/pg_proc.dat
index 33c995cd06..1087b6cc01 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 => 'local_sequenceam_handler', provolatile => 'v',
+  prorettype => 'sequence_am_handler', proargtypes => 'internal',
+  prosrc => 'local_sequenceam_handler' },
+
 # Index access method handlers
 { oid => '330', descr => 'btree index access method handler',
   proname => 'bthandler', provolatile => 'v', prorettype => 'index_am_handler',
@@ -7598,6 +7604,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 f6110a850d..46d70c4cc4 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -626,6 +626,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 179eb9901f..4a6329ee51 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -141,6 +141,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 7db7b3da7b..1ff652848d 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index 626dc696d5..4a5ab87c2f 100644
--- a/src/include/nodes/meson.build
+++ b/src/include/nodes/meson.build
@@ -9,6 +9,7 @@ node_support_input_i = [
   '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/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6947225b64..aab9bfa709 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2972,6 +2972,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 3d74483f44..c13b8955a2 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -53,6 +53,8 @@ extern bool check_debug_io_direct(char **newval, void **extra, GucSource source)
 extern void assign_debug_io_direct(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 0ad613c4b8..df226ad6f2 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -187,6 +187,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 697f89905e..b89a7e0526 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = local.o sequence.o
+OBJS = local.o sequence.o sequenceamapi.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/sequence/local.c b/src/backend/access/sequence/local.c
index e77f25e13e..2d20abc58e 100644
--- a/src/backend/access/sequence/local.c
+++ b/src/backend/access/sequence/local.c
@@ -18,6 +18,7 @@
 #include "access/bufmask.h"
 #include "access/localam.h"
 #include "access/multixact.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -25,6 +26,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -297,15 +299,15 @@ local_seq_redo(XLogReaderState *record)
 }
 
 /*
- * local_seq_nextval()
+ * local_nextval()
  *
  * Allocate a new value for a local sequence, based on the sequence
  * configuration.
  */
-int64
-local_seq_nextval(Relation rel, int64 incby, int64 maxv,
-				  int64 minv, int64 cache, bool cycle,
-				  int64 *last)
+static int64
+local_nextval(Relation rel, int64 incby, int64 maxv,
+			  int64 minv, int64 cache, bool cycle,
+			  int64 *last)
 {
 	int64		result;
 	int64		fetch;
@@ -485,18 +487,18 @@ local_seq_nextval(Relation rel, int64 incby, int64 maxv,
 }
 
 /*
- * local_seq_get_table_am()
+ * local_get_table_am()
  *
  * Return the table access method used by this sequence.
  */
-const char *
-local_seq_get_table_am(void)
+static const char *
+local_get_table_am(void)
 {
 	return "heap";
 }
 
 /*
- * local_seq_init()
+ * 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
@@ -504,8 +506,8 @@ local_seq_get_table_am(void)
  * inserted after the relation has been created, filling in its heap
  * table.
  */
-void
-local_seq_init(Relation rel, int64 last_value, bool is_called)
+static void
+local_init(Relation rel, int64 last_value, bool is_called)
 {
 	Datum		value[SEQ_COL_LASTCOL];
 	bool		null[SEQ_COL_LASTCOL];
@@ -567,12 +569,12 @@ local_seq_init(Relation rel, int64 last_value, bool is_called)
 }
 
 /*
- * local_seq_setval()
+ * local_setval()
  *
  * Callback for setval().
  */
-void
-local_seq_setval(Relation rel, int64 next, bool iscalled)
+static void
+local_setval(Relation rel, int64 next, bool iscalled)
 {
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
@@ -614,13 +616,13 @@ local_seq_setval(Relation rel, int64 next, bool iscalled)
 }
 
 /*
- * local_seq_reset()
+ * local_reset()
  *
  * Perform a hard reset on the local sequence, rewriting its heap data
  * entirely.
  */
-void
-local_seq_reset(Relation rel, int64 startv, bool is_called, bool reset_state)
+static void
+local_reset(Relation rel, int64 startv, bool is_called, bool reset_state)
 {
 	Form_pg_sequence_data seq;
 	Buffer		buf;
@@ -668,12 +670,12 @@ local_seq_reset(Relation rel, int64 startv, bool is_called, bool reset_state)
 }
 
 /*
- * local_seq_get_state()
+ * local_get_state()
  *
  * Retrieve the state of a local sequence.
  */
-void
-local_seq_get_state(Relation rel, int64 *last_value, bool *is_called)
+static void
+local_get_state(Relation rel, int64 *last_value, bool *is_called)
 {
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
@@ -689,12 +691,12 @@ local_seq_get_state(Relation rel, int64 *last_value, bool *is_called)
 }
 
 /*
- * local_seq_change_persistence()
+ * local_change_persistence()
  *
  * Persistence change for the local sequence Relation.
  */
-void
-local_seq_change_persistence(Relation rel, char newrelpersistence)
+static void
+local_change_persistence(Relation rel, char newrelpersistence)
 {
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
@@ -704,3 +706,24 @@ local_seq_change_persistence(Relation rel, char newrelpersistence)
 	fill_seq_with_data(rel, &seqdatatuple);
 	UnlockReleaseBuffer(buf);
 }
+
+/* ------------------------------------------------------------------------
+ * Definition of the local sequence access method.
+ * ------------------------------------------------------------------------
+ */
+static const SequenceAmRoutine local_methods = {
+	.type = T_SequenceAmRoutine,
+	.get_table_am = local_get_table_am,
+	.init = local_init,
+	.nextval = local_nextval,
+	.setval = local_setval,
+	.reset = local_reset,
+	.get_state = local_get_state,
+	.change_persistence = local_change_persistence
+};
+
+Datum
+local_sequenceam_handler(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_POINTER(&local_methods);
+}
diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build
index b271956232..feae8e5884 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -3,4 +3,5 @@
 backend_sources += files(
   'local.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/sequence.c b/src/backend/access/sequence/sequence.c
index a5b9fccb50..64e023f0b4 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 0000000000..4314b9ecb5
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2023, 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/xact.h"
+#include "access/sequenceam.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 7224d96695..6aa0a3f9e7 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1452,7 +1452,8 @@ 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.
 		 */
-		if (RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE)
+		if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE) ||
+			relkind == RELKIND_SEQUENCE)
 		{
 			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 2050619123..688b2163d3 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 f1b0c484e2..24a515441a 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/localam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -152,6 +152,7 @@ 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;
@@ -173,7 +174,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	rel = sequence_open(seqoid, AccessExclusiveLock);
 
 	/* now initialize the sequence table structure and its data */
-	local_seq_init(rel, last_value, is_called);
+	sequence_init(rel, last_value, is_called);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -241,7 +242,7 @@ ResetSequence(Oid seq_relid)
 	ReleaseSysCache(pgstuple);
 
 	/* Sequence state is forcibly reset here. */
-	local_seq_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 */
@@ -297,7 +298,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
 
 	/* Read sequence data */
-	local_seq_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -310,7 +311,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		if (RelationNeedsWAL(seqrel))
 			GetTopTransactionId();
 
-		local_seq_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 */
@@ -346,7 +347,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence)
 	if (RelationNeedsWAL(seqrel))
 		GetTopTransactionId();
 
-	local_seq_change_persistence(seqrel, newrelpersistence);
+	sequence_change_persistence(seqrel, newrelpersistence);
 
 	sequence_close(seqrel, NoLock);
 }
@@ -463,8 +464,8 @@ nextval_internal(Oid relid, bool check_permissions)
 	ReleaseSysCache(pgstuple);
 
 	/* retrieve next value from the access method */
-	result = local_seq_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;
@@ -618,7 +619,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 		GetTopTransactionId();
 
 	/* Call the access method callback */
-	local_seq_setval(seqrel, next, iscalled);
+	sequence_setval(seqrel, next, iscalled);
 
 	sequence_close(seqrel, NoLock);
 }
@@ -1334,7 +1335,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 				 errmsg("permission denied for sequence %s",
 						RelationGetRelationName(seqrel))));
 
-	local_seq_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 	sequence_close(seqrel, NoLock);
 
 	values[0] = BoolGetDatum(is_called);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 847468705c..84aef1b952 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -22,6 +22,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"
@@ -957,10 +958,17 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	}
 	else if (RELKIND_HAS_TABLE_AM(relkind))
 		accessMethod = default_table_access_method;
+	else if (relkind == RELKIND_SEQUENCE)
+		accessMethod = default_sequence_access_method;
 
-	/* look up the access method, verify it is for a table */
+	/* look up the access method, verify it is for a table or a sequence */
 	if (accessMethod != NULL)
-		accessMethodId = get_table_am_oid(accessMethod, false);
+	{
+		if (relkind == RELKIND_SEQUENCE)
+			accessMethodId = get_sequence_am_oid(accessMethod, false);
+		else
+			accessMethodId = get_table_am_oid(accessMethod, false);
+	}
 
 	/*
 	 * Create the relation.  Inherited defaults and constraints are passed in
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index ebbe9052cb..31dfd9c233 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -48,6 +48,7 @@ node_headers = \
 	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/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 72c7963578..b642eca278 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -59,6 +59,7 @@ my @all_input_files = qw(
   nodes/execnodes.h
   access/amapi.h
   access/sdir.h
+  access/sequenceam.h
   access/tableam.h
   access/tsmapi.h
   commands/event_trigger.h
@@ -83,6 +84,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 d631ac89a9..23c06daca6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -388,6 +388,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
@@ -4753,23 +4754,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;
@@ -4806,6 +4810,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); }
 		;
@@ -5802,6 +5811,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 cf0d432ab1..1e1633a01c 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"
@@ -461,6 +462,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
 	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 3ba8cb192c..15e065d755 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -372,6 +372,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 b3faccbefe..8504bb52f7 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -35,6 +35,7 @@
 #include "access/nbtree.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"
@@ -66,6 +67,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"
@@ -301,6 +303,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);
@@ -1206,9 +1209,10 @@ 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_SEQUENCE)
+		RelationInitSequenceAccessMethod(relation);
 	else
 		Assert(relation->rd_rel->relam == InvalidOid);
 
@@ -1805,17 +1809,7 @@ 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))
+	if (IsCatalogRelation(relation))
 	{
 		/*
 		 * Avoid doing a syscache lookup for catalog tables.
@@ -1846,6 +1840,47 @@ 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;
+
+	/*
+	 * 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
  *
@@ -3687,14 +3722,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);
 
 	/*
 	 * Okay to insert into the relcache hash table.
@@ -4307,13 +4345,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);
@@ -6313,8 +6359,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);
@@ -6326,6 +6374,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_tables.c b/src/backend/utils/misc/guc_tables.c
index 6474e35ec0..6d54deda8d 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -29,6 +29,7 @@
 #include "access/commit_ts.h"
 #include "access/gin.h"
 #include "access/toast_compression.h"
+#include "access/sequenceam.h"
 #include "access/twophase.h"
 #include "access/xlog_internal.h"
 #include "access/xlogprefetcher.h"
@@ -3999,6 +4000,17 @@ struct config_string ConfigureNamesString[] =
 		check_default_table_access_method, NULL, NULL
 	},
 
+	{
+		{"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets the default sequence access method for new sequences."),
+			NULL,
+			GUC_IS_NAME
+		},
+		&default_sequence_access_method,
+		DEFAULT_SEQUENCE_ACCESS_METHOD,
+		check_default_sequence_access_method, NULL, NULL
+	},
+
 	{
 		{"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default tablespace to create tables and indexes in."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf9f283cfe..e3e46923cf 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -684,6 +684,7 @@
 					#   error
 #search_path = '"$user", public'	# schema names
 #row_security = on
+#default_sequence_access_method = 'local'
 #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/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index 029a0d0521..7be589d92e 100644
--- a/src/bin/pg_waldump/t/001_basic.pl
+++ b/src/bin/pg_waldump/t/001_basic.pl
@@ -41,7 +41,7 @@ Btree
 Hash
 Gin
 Gist
-Sequence
+LocalSequence
 SPGist
 BRIN
 CommitTs
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 5077e7b358..aa8c6c73e6 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -161,10 +161,12 @@ describeAccessMethods(const char *pattern, bool verbose)
 					  "SELECT amname AS \"%s\",\n"
 					  "  CASE amtype"
 					  " WHEN 'i' THEN '%s'"
+					  " WHEN 's' THEN '%s'"
 					  " WHEN 't' 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.c b/src/bin/psql/tab-complete.c
index 049801186c..752208b6a2 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2197,7 +2197,7 @@ psql_completion(const char *text, int start, int end)
 	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");
@@ -3204,7 +3204,7 @@ psql_completion(const char *text, int start, int end)
 	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 b50293d514..dfcc9cff49 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;
@@ -331,9 +326,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 = 'local';
 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;
@@ -356,7 +354,7 @@ ORDER BY 3, 1, 2;
  r       | heap2  | tableam_parted_1_heapx
  r       | heap   | tableam_parted_2_heapx
  p       |        | tableam_parted_heapx
- S       |        | tableam_seq_heapx
+ S       | local  | tableam_seq_heapx
  r       | heap2  | tableam_tbl_heapx
  r       | heap2  | tableam_tblas_heapx
  m       | heap2  | tableam_tblmv_heapx
@@ -388,3 +386,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 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 7610b011d6..12f48e4beb 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1929,6 +1929,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 13e4f6db7b..7987dd0586 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4958,8 +4958,8 @@ Indexes:
 -- check printing info about access methods
 \dA
 List of access methods
-  Name  | Type  
---------+-------
+  Name  |   Type   
+--------+----------
  brin   | Index
  btree  | Index
  gin    | Index
@@ -4967,13 +4967,14 @@ List of access methods
  hash   | Index
  heap   | Table
  heap2  | Table
+ local  | Sequence
  spgist | Index
-(8 rows)
+(9 rows)
 
 \dA *
 List of access methods
-  Name  | Type  
---------+-------
+  Name  |   Type   
+--------+----------
  brin   | Index
  btree  | Index
  gin    | Index
@@ -4981,8 +4982,9 @@ List of access methods
  hash   | Index
  heap   | Table
  heap2  | Table
+ local  | Sequence
  spgist | Index
-(8 rows)
+(9 rows)
 
 \dA h*
 List of access methods
@@ -5007,32 +5009,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     | 
+ local  | Sequence | 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     | 
+ local  | Sequence | 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/sql/create_am.sql b/src/test/regress/sql/create_am.sql
index 2785ffd8bb..6b180519aa 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;
@@ -222,9 +219,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 = 'local';
 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;
@@ -257,3 +258,16 @@ CREATE TABLE i_am_a_failure() USING "btree";
 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 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 2fe7b6dcc4..1409622374 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1229,6 +1229,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/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 38a86575e1..c31d351392 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2492,6 +2492,7 @@ SeqScan
 SeqScanState
 SeqTable
 SeqTableData
+SequenceAmRoutine
 SerCommitSeqNo
 SerialControl
 SerialIOData
@@ -3462,6 +3463,7 @@ lineno_t
 list_sort_comparator
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 locale_t
 locate_agg_of_level_context
@@ -3725,7 +3727,6 @@ save_buffer
 scram_state
 scram_state_enum
 sem_t
-sequence_magic
 set_join_pathlist_hook_type
 set_rel_pathlist_hook_type
 shm_mq
@@ -3942,6 +3943,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
@@ -3953,7 +3955,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.43.0

v2-0008-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From 83df181f9c8512d887e427e1d8c90ef081db1303 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v2 08/10] 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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 94d1eb2b81..f4d527efed 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8707,6 +8707,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 bd42b3ef16..62a4a233b4 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -96,6 +96,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 f31dc2094a..eefa350e1d 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;
   &generic-wal;
   &custom-rmgr;
   &btree;
diff --git a/doc/src/sgml/ref/create_access_method.sgml b/doc/src/sgml/ref/create_access_method.sgml
index dae43dbaed..3067dc4d4d 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>SEQUENCE</literal> and
+      <literal>INDEX</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 34e9084b5c..d00bdabde0 100644
--- a/doc/src/sgml/ref/create_sequence.sgml
+++ b/doc/src/sgml/ref/create_sequence.sgml
@@ -27,6 +27,7 @@ CREATE [ { TEMPORARY | TEMP } | UNLOGGED ] SEQUENCE [ IF NOT EXISTS ] <replaceab
     [ MINVALUE <replaceable class="parameter">minvalue</replaceable> | NO MINVALUE ] [ MAXVALUE <replaceable class="parameter">maxvalue</replaceable> | NO MAXVALUE ]
     [ START [ WITH ] <replaceable class="parameter">start</replaceable> ] [ CACHE <replaceable class="parameter">cache</replaceable> ] [ [ NO ] CYCLE ]
     [ OWNED BY { <replaceable class="parameter">table_name</replaceable>.<replaceable class="parameter">column_name</replaceable> | NONE } ]
+    [ USING <replaceable class="parameter">access_method</replaceable> ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -261,6 +262,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 0000000000..a96170bfac
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.43.0

v2-0009-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From 1f0bac9ff9defee7207a84cc5982fe52c8ebe0e6 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:56:26 +0900
Subject: [PATCH v2 09/10] 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            | 39 ++++++++++++++--
 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     | 63 ++++++++++++++++++++++----
 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, 210 insertions(+), 14 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 9ef2f2017e..a838e6490c 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -95,6 +95,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
@@ -183,6 +184,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 256d1e35a4..0899e3bea9 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -171,6 +171,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;
@@ -1118,6 +1119,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->owner = opts->owner ? pg_strdup(opts->owner) : NULL;
 	newToc->desc = pg_strdup(opts->description);
@@ -2258,6 +2260,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 */
 
@@ -2487,6 +2490,7 @@ WriteToc(ArchiveHandle *AH)
 		WriteStr(AH, te->copyStmt);
 		WriteStr(AH, te->namespace);
 		WriteStr(AH, te->tablespace);
+		WriteStr(AH, te->sequenceam);
 		WriteStr(AH, te->tableam);
 		WriteStr(AH, te->owner);
 		WriteStr(AH, "false");
@@ -2590,6 +2594,9 @@ ReadToc(ArchiveHandle *AH)
 		if (AH->version >= K_VERS_1_10)
 			te->tablespace = ReadStr(AH);
 
+		if (AH->version >= K_VERS_1_16)
+			te->sequenceam = ReadStr(AH);
+
 		if (AH->version >= K_VERS_1_14)
 			te->tableam = ReadStr(AH);
 
@@ -3226,6 +3233,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;
 
@@ -3388,6 +3398,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.
  */
@@ -3547,6 +3608,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
 	_becomeOwner(AH, te);
 	_selectOutputSchema(AH, te->namespace);
 	_selectTablespace(AH, te->tablespace);
+	_selectSequenceAccessMethod(AH, te->sequenceam);
 	_selectTableAccessMethod(AH, te->tableam);
 
 	/* Emit header comment for item */
@@ -4003,6 +4065,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);
@@ -4738,6 +4802,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -4787,6 +4852,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 917283fd34..d29a22ac40 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -68,10 +68,11 @@
 #define K_VERS_1_15 MAKE_ARCHIVE_VERSION(1, 15, 0)	/* add
 													 * compression_algorithm
 													 * in header */
+#define K_VERS_1_16 MAKE_ARCHIVE_VERSION(1, 16, 0)	/* add sequenceam */
 
 /* Current archive version number (the format we can output) */
 #define K_VERS_MAJOR 1
-#define K_VERS_MINOR 15
+#define K_VERS_MINOR 16
 #define K_VERS_REV 0
 #define K_VERS_SELF MAKE_ARCHIVE_VERSION(K_VERS_MAJOR, K_VERS_MINOR, K_VERS_REV)
 
@@ -319,6 +320,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 */
 
@@ -347,6 +349,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	   *owner;
 	char	   *desc;
@@ -387,6 +390,7 @@ typedef struct _archiveOpts
 	const char *tag;
 	const char *namespace;
 	const char *tablespace;
+	const char *sequenceam;
 	const char *tableam;
 	const char *owner;
 	const char *description;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c7612c3793..7e3da007da 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -411,6 +411,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},
@@ -1015,6 +1016,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;
@@ -1130,6 +1132,7 @@ help(const char *progname)
 	printf(_("  --no-publications            do not dump publications\n"));
 	printf(_("  --no-security-labels         do not dump security label assignments\n"));
 	printf(_("  --no-subscriptions           do not dump subscriptions\n"));
+	printf(_("  --no-sequence-access-method  do not 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"));
@@ -12995,6 +12998,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;
@@ -17211,7 +17217,8 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 			   *maxv,
 			   *minv,
 			   *cache,
-			   *seqtype;
+			   *seqtype,
+			   *seqam;
 	bool		cycled;
 	bool		is_ascending;
 	int64		default_minv,
@@ -17225,13 +17232,35 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
-	if (fout->remoteVersion >= 100000)
+	if (fout->remoteVersion >= 170000)
 	{
+		/*
+		 * PostgreSQL 17 has added support for sequence access methods.
+		 */
+		appendPQExpBuffer(query,
+						  "SELECT format_type(s.seqtypid, NULL), "
+						  "s.seqstart, s.seqincrement, "
+						  "s.seqmax, s.seqmin, "
+						  "s.seqcache, s.seqcycle, "
+						  "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) "
+						  "WHERE s.seqrelid = '%u'::oid",
+						  tbinfo->dobj.catId.oid);
+	}
+	else if (fout->remoteVersion >= 100000)
+	{
+		/*
+		 * PostgreSQL 10 has moved sequence metadata to the catalog
+		 * pg_sequence.
+		 */
 		appendPQExpBuffer(query,
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
+						  "seqcache, seqcycle, "
+						  "'local' AS seqam "
 						  "FROM pg_catalog.pg_sequence "
 						  "WHERE seqrelid = '%u'::oid",
 						  tbinfo->dobj.catId.oid);
@@ -17247,7 +17276,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 		appendPQExpBuffer(query,
 						  "SELECT 'bigint' AS sequence_type, "
 						  "start_value, increment_by, max_value, min_value, "
-						  "cache_value, is_cycled FROM %s",
+						  "cache_value, is_cycled, 'local' as seqam FROM %s",
 						  fmtQualifiedDumpable(tbinfo));
 	}
 
@@ -17266,6 +17295,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	minv = PQgetvalue(res, 0, 4);
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
+	seqam = PQgetvalue(res, 0, 7);
 
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
@@ -17397,6 +17427,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 					 ARCHIVE_OPTS(.tag = tbinfo->dobj.name,
 								  .namespace = tbinfo->dobj.namespace->dobj.name,
 								  .owner = tbinfo->rolname,
+								  .sequenceam = 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 92389353a4..5c96f3eee1 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -99,6 +99,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;
@@ -163,6 +164,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},
@@ -437,6 +439,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)
@@ -670,6 +674,7 @@ help(void)
 	printf(_("  --no-security-labels         do not dump security label assignments\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 c3beacdec1..0049130535 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -68,6 +68,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;
@@ -115,6 +116,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},
@@ -351,6 +353,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;
@@ -479,6 +482,7 @@ usage(const char *progname)
 	printf(_("  --no-publications            do not restore publications\n"));
 	printf(_("  --no-security-labels         do not restore security labels\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(_("  --section=SECTION            restore named section (pre-data, data, or post-data)\n"));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index eb3ec534b4..6557e44ba6 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -548,6 +548,13 @@ 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', 'postgres',
+		],
+	},
 	no_table_access_method => {
 		dump_cmd => [
 			'pg_dump', '--no-sync',
@@ -722,6 +729,7 @@ my %full_runs = (
 	no_large_objects => 1,
 	no_owner => 1,
 	no_privs => 1,
+	no_sequence_access_method => 1,
 	no_table_access_method => 1,
 	pg_dumpall_dbprivs => 1,
 	pg_dumpall_exclude => 1,
@@ -3829,9 +3837,7 @@ my %tests = (
 		\QCREATE INDEX measurement_city_id_logdate_idx ON ONLY dump_test.measurement USING\E
 		/xm,
 		like => {
-			%full_runs,
-			%dump_test_schema_runs,
-			section_post_data => 1,
+			%full_runs, %dump_test_schema_runs, section_post_data => 1,
 		},
 		unlike => {
 			exclude_dump_test_schema => 1,
@@ -4500,6 +4506,18 @@ my %tests = (
 		},
 	},
 
+	'CREATE ACCESS METHOD regress_test_sequence_am' => {
+		create_order => 11,
+		create_sql =>
+		  'CREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER local_sequenceam_handler;',
+		regexp => qr/^
+			\QCREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER 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
@@ -4528,6 +4546,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 local;
+			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 local;',
+		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 => '
@@ -4722,10 +4769,8 @@ $node->command_fails_like(
 ##############################################################
 # Test dumping pg_catalog (for research -- cannot be reloaded)
 
-$node->command_ok(
-	[ 'pg_dump', '-p', "$port", '-n', 'pg_catalog' ],
-	'pg_dump: option -n pg_catalog'
-);
+$node->command_ok([ 'pg_dump', '-p', "$port", '-n', 'pg_catalog' ],
+	'pg_dump: option -n pg_catalog');
 
 #########################################
 # Test valid database exclusion patterns
@@ -4887,8 +4932,8 @@ foreach my $run (sort keys %pgdump_runs)
 		}
 		# Check for useless entries in "unlike" list.  Runs that are
 		# not listed in "like" don't need to be excluded in "unlike".
-		if ($tests{$test}->{unlike}->{$test_key} &&
-			!defined($tests{$test}->{like}->{$test_key}))
+		if ($tests{$test}->{unlike}->{$test_key}
+			&& !defined($tests{$test}->{like}->{$test_key}))
 		{
 			die "useless \"unlike\" entry \"$test_key\" in test \"$test\"";
 		}
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 0e5ba4f712..0dd0f5e0b4 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1083,6 +1083,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 4d7c046468..34643175fb 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -479,6 +479,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 1a23874da6..6581cff721 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -733,6 +733,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.43.0

v2-0010-dummy_sequence_am-Example-of-sequence-AM.patchtext/x-diff; charset=us-asciiDownload
From 1bdc9ef40414c049edec154903c40d317387d1c3 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:56:52 +0900
Subject: [PATCH v2 10/10] dummy_sequence_am: Example of sequence AM

---
 src/test/modules/Makefile                     |   1 +
 src/test/modules/dummy_sequence_am/.gitignore |   3 +
 src/test/modules/dummy_sequence_am/Makefile   |  19 +++
 .../dummy_sequence_am--1.0.sql                |  13 +++
 .../dummy_sequence_am/dummy_sequence_am.c     | 110 ++++++++++++++++++
 .../dummy_sequence_am.control                 |   5 +
 .../expected/dummy_sequence.out               |  35 ++++++
 .../modules/dummy_sequence_am/meson.build     |  33 ++++++
 .../dummy_sequence_am/sql/dummy_sequence.sql  |  17 +++
 src/test/modules/meson.build                  |   1 +
 10 files changed, 237 insertions(+)
 create mode 100644 src/test/modules/dummy_sequence_am/.gitignore
 create mode 100644 src/test/modules/dummy_sequence_am/Makefile
 create mode 100644 src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql
 create mode 100644 src/test/modules/dummy_sequence_am/dummy_sequence_am.c
 create mode 100644 src/test/modules/dummy_sequence_am/dummy_sequence_am.control
 create mode 100644 src/test/modules/dummy_sequence_am/expected/dummy_sequence.out
 create mode 100644 src/test/modules/dummy_sequence_am/meson.build
 create mode 100644 src/test/modules/dummy_sequence_am/sql/dummy_sequence.sql

diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 5d33fa6a9a..81f857599e 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -10,6 +10,7 @@ SUBDIRS = \
 		  delay_execution \
 		  dummy_index_am \
 		  dummy_seclabel \
+		  dummy_sequence_am \
 		  libpq_pipeline \
 		  plsample \
 		  spgist_name_ops \
diff --git a/src/test/modules/dummy_sequence_am/.gitignore b/src/test/modules/dummy_sequence_am/.gitignore
new file mode 100644
index 0000000000..44d119cfcc
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/.gitignore
@@ -0,0 +1,3 @@
+# Generated subdirectories
+/log/
+/results/
diff --git a/src/test/modules/dummy_sequence_am/Makefile b/src/test/modules/dummy_sequence_am/Makefile
new file mode 100644
index 0000000000..391f7ac946
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/Makefile
@@ -0,0 +1,19 @@
+# src/test/modules/dummy_sequence_am/Makefile
+
+MODULES = dummy_sequence_am
+
+EXTENSION = dummy_sequence_am
+DATA = dummy_sequence_am--1.0.sql
+
+REGRESS = dummy_sequence
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/dummy_sequence_am
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql b/src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql
new file mode 100644
index 0000000000..e12b1f9d87
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql
@@ -0,0 +1,13 @@
+/* src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION dummy_sequence_am" to load this file. \quit
+
+CREATE FUNCTION dummy_sequenceam_handler(internal)
+  RETURNS sequence_am_handler
+  AS 'MODULE_PATHNAME'
+  LANGUAGE C;
+
+CREATE ACCESS METHOD dummy_sequence_am
+  TYPE SEQUENCE HANDLER dummy_sequenceam_handler;
+COMMENT ON ACCESS METHOD dummy_sequence_am IS 'dummy sequence access method';
diff --git a/src/test/modules/dummy_sequence_am/dummy_sequence_am.c b/src/test/modules/dummy_sequence_am/dummy_sequence_am.c
new file mode 100644
index 0000000000..b5ee5d89da
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/dummy_sequence_am.c
@@ -0,0 +1,110 @@
+/*-------------------------------------------------------------------------
+ *
+ * dummy_sequence_am.c
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/test/modules/dummy_sequence_am/dummy_sequence_am.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/sequenceam.h"
+#include "fmgr.h"
+
+PG_MODULE_MAGIC;
+
+/* this sequence is fully on-memory */
+static int	dummy_seqam_last_value = 1;
+static bool dummy_seqam_is_called = false;
+
+PG_FUNCTION_INFO_V1(dummy_sequenceam_handler);
+
+
+/* ------------------------------------------------------------------------
+ * Callbacks for the dummy 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 *
+dummy_sequenceam_get_table_am(void)
+{
+	return "heap";
+}
+
+static void
+dummy_sequenceam_init(Relation rel, int64 last_value, bool is_called)
+{
+	dummy_seqam_last_value = last_value;
+	dummy_seqam_is_called = is_called;
+}
+
+static int64
+dummy_sequenceam_nextval(Relation rel, int64 incby, int64 maxv,
+						 int64 minv, int64 cache, bool cycle,
+						 int64 *last)
+{
+	dummy_seqam_last_value += incby;
+	dummy_seqam_is_called = true;
+
+	return dummy_seqam_last_value;
+}
+
+static void
+dummy_sequenceam_setval(Relation rel, int64 next, bool iscalled)
+{
+	dummy_seqam_last_value = next;
+	dummy_seqam_is_called = iscalled;
+}
+
+static void
+dummy_sequenceam_get_state(Relation rel, int64 *last_value, bool *is_called)
+{
+	*last_value = dummy_seqam_last_value;
+	*is_called = dummy_seqam_is_called;
+}
+
+static void
+dummy_sequenceam_reset(Relation rel, int64 startv, bool is_called,
+					   bool reset_state)
+{
+	dummy_seqam_last_value = startv;
+	dummy_seqam_is_called = is_called;
+}
+
+static void
+dummy_sequenceam_change_persistence(Relation rel, char newrelpersistence)
+{
+	/* nothing to do, really */
+}
+
+/* ------------------------------------------------------------------------
+ * Definition of the dummy sequence access method.
+ * ------------------------------------------------------------------------
+ */
+
+static const SequenceAmRoutine dummy_sequenceam_methods = {
+	.type = T_SequenceAmRoutine,
+	.get_table_am = dummy_sequenceam_get_table_am,
+	.init = dummy_sequenceam_init,
+	.nextval = dummy_sequenceam_nextval,
+	.setval = dummy_sequenceam_setval,
+	.get_state = dummy_sequenceam_get_state,
+	.reset = dummy_sequenceam_reset,
+	.change_persistence = dummy_sequenceam_change_persistence
+};
+
+Datum
+dummy_sequenceam_handler(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_POINTER(&dummy_sequenceam_methods);
+}
diff --git a/src/test/modules/dummy_sequence_am/dummy_sequence_am.control b/src/test/modules/dummy_sequence_am/dummy_sequence_am.control
new file mode 100644
index 0000000000..9f10622f2f
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/dummy_sequence_am.control
@@ -0,0 +1,5 @@
+# dummy_sequence_am extension
+comment = 'dummy_sequence_am - sequence access method template'
+default_version = '1.0'
+module_pathname = '$libdir/dummy_sequence_am'
+relocatable = true
diff --git a/src/test/modules/dummy_sequence_am/expected/dummy_sequence.out b/src/test/modules/dummy_sequence_am/expected/dummy_sequence.out
new file mode 100644
index 0000000000..57588cea5b
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/expected/dummy_sequence.out
@@ -0,0 +1,35 @@
+CREATE EXTENSION dummy_sequence_am;
+CREATE SEQUENCE dummyseq USING dummy_sequence_am;
+SELECT nextval('dummyseq'::regclass);
+ nextval 
+---------
+       2
+(1 row)
+
+SELECT setval('dummyseq'::regclass, 14);
+ setval 
+--------
+     14
+(1 row)
+
+SELECT nextval('dummyseq'::regclass);
+ nextval 
+---------
+      15
+(1 row)
+
+-- Sequence relation exists, but it has no attributes.
+SELECT * FROM dummyseq;
+--
+(0 rows)
+
+-- Reset connection, which will reset the sequence
+\c
+SELECT nextval('dummyseq'::regclass);
+ nextval 
+---------
+       2
+(1 row)
+
+DROP SEQUENCE dummyseq;
+DROP EXTENSION dummy_sequence_am;
diff --git a/src/test/modules/dummy_sequence_am/meson.build b/src/test/modules/dummy_sequence_am/meson.build
new file mode 100644
index 0000000000..84460070e4
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+dummy_sequence_am_sources = files(
+  'dummy_sequence_am.c',
+)
+
+if host_system == 'windows'
+  dummy_sequence_am_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dummy_sequence_am',
+    '--FILEDESC', 'dummy_sequence_am - sequence access method template',])
+endif
+
+dummy_sequence_am = shared_module('dummy_sequence_am',
+  dummy_sequence_am_sources,
+  kwargs: pg_test_mod_args,
+)
+test_install_libs += dummy_sequence_am
+
+test_install_data += files(
+  'dummy_sequence_am.control',
+  'dummy_sequence_am--1.0.sql',
+)
+
+tests += {
+  'name': 'dummy_sequence_am',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'regress': {
+    'sql': [
+      'dummy_sequence',
+    ],
+  },
+}
diff --git a/src/test/modules/dummy_sequence_am/sql/dummy_sequence.sql b/src/test/modules/dummy_sequence_am/sql/dummy_sequence.sql
new file mode 100644
index 0000000000..c739b29a46
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/sql/dummy_sequence.sql
@@ -0,0 +1,17 @@
+CREATE EXTENSION dummy_sequence_am;
+
+CREATE SEQUENCE dummyseq USING dummy_sequence_am;
+
+SELECT nextval('dummyseq'::regclass);
+SELECT setval('dummyseq'::regclass, 14);
+SELECT nextval('dummyseq'::regclass);
+
+-- Sequence relation exists, but it has no attributes.
+SELECT * FROM dummyseq;
+
+-- Reset connection, which will reset the sequence
+\c
+SELECT nextval('dummyseq'::regclass);
+
+DROP SEQUENCE dummyseq;
+DROP EXTENSION dummy_sequence_am;
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index b76f588559..9a4c4ac506 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -5,6 +5,7 @@ subdir('commit_ts')
 subdir('delay_execution')
 subdir('dummy_index_am')
 subdir('dummy_seclabel')
+subdir('dummy_sequence_am')
 subdir('ldap_password_func')
 subdir('libpq_pipeline')
 subdir('plsample')
-- 
2.43.0

#3Peter Eisentraut
peter@eisentraut.org
In reply to: Michael Paquier (#1)
Re: Sequence Access Methods, round two

On 01.12.23 06:00, Michael Paquier wrote:

Please find attached a patch set that aims at implementing sequence
access methods, with callbacks following a model close to table and
index AMs, with a few cases in mind:
- Global sequences (including range-allocation, local caching).
- Local custom computations (a-la-snowflake).

That's a lot of code, but the use cases are summarized in two lines?!?

I would like to see a lot more elaboration what these uses cases are (I
recognize the words, but do we have the same interpretation of them?)
and how they would be addressed by what you are proposing, and better
yet an actual implementation of something useful, rather than just a
dummy test module.

#4Matthias van de Meent
boekewurm+postgres@gmail.com
In reply to: Peter Eisentraut (#3)
Re: Sequence Access Methods, round two

On Thu, 18 Jan 2024, 16:06 Peter Eisentraut, <peter@eisentraut.org> wrote:

On 01.12.23 06:00, Michael Paquier wrote:

Please find attached a patch set that aims at implementing sequence
access methods, with callbacks following a model close to table and
index AMs, with a few cases in mind:
- Global sequences (including range-allocation, local caching).
- Local custom computations (a-la-snowflake).

That's a lot of code, but the use cases are summarized in two lines?!?

I would like to see a lot more elaboration what these uses cases are (I
recognize the words, but do we have the same interpretation of them?)
and how they would be addressed by what you are proposing, and better
yet an actual implementation of something useful, rather than just a
dummy test module.

At $prevjob we had a use case for PRNG to generate small,
non-sequential "random" numbers without the birthday problem occurring
in sqrt(option space) because that'd increase the printed length of
the numbers beyond a set limit. The sequence API proposed here
would've been a great alternative to the solution we found, as it
would allow a sequence to be backed by an Linear Congruential
Generator directly, rather than the implementation of our own
transactional random_sequence table.

Kind regards,

Matthias van de Meent
Neon (https://neon.tech)

#5Michael Paquier
michael@paquier.xyz
In reply to: Matthias van de Meent (#4)
Re: Sequence Access Methods, round two

On Thu, Jan 18, 2024 at 04:54:06PM +0100, Matthias van de Meent wrote:

On Thu, 18 Jan 2024, 16:06 Peter Eisentraut, <peter@eisentraut.org> wrote:

On 01.12.23 06:00, Michael Paquier wrote:

Please find attached a patch set that aims at implementing sequence
access methods, with callbacks following a model close to table and
index AMs, with a few cases in mind:
- Global sequences (including range-allocation, local caching).
- Local custom computations (a-la-snowflake).

That's a lot of code, but the use cases are summarized in two lines?!?

I would like to see a lot more elaboration what these uses cases are (I
recognize the words, but do we have the same interpretation of them?)
and how they would be addressed by what you are proposing, and better
yet an actual implementation of something useful, rather than just a
dummy test module.

At $prevjob we had a use case for PRNG to generate small,
non-sequential "random" numbers without the birthday problem occurring
in sqrt(option space) because that'd increase the printed length of
the numbers beyond a set limit. The sequence API proposed here
would've been a great alternative to the solution we found, as it
would allow a sequence to be backed by an Linear Congruential
Generator directly, rather than the implementation of our own
transactional random_sequence table.

Interesting.

Yes, one of the advantages of this API layer is that all the
computation is hidden behind a sequence object at the PostgreSQL
level, hence applications just need to set a GUC to select a given
computation method *while* still using the same DDLs from their
application, or just append USING to their CREATE SEQUENCE but I've
heard that applications would just do the former and forget about it.

The reason why this stuff has bumped into my desk is that we have no
good solution in-core for globally-distributed transactions for
active-active deployments. First, anything we have needs to be
plugged into default expressions of attributes like with [1]https://github.com/pgEdge/snowflake or [2]/messages/by-id/TY3PR01MB988983D23E4F1DA10567BC5BF5B9A@TY3PR01MB9889.jpnprd01.prod.outlook.com -- Michael,
or a tweak is to use sequence values that are computed with different
increments to avoid value overlaps across nodes. Both of these
require application changes, which is meh for a bunch of users. The
second approach with integer-based values can be become particularly a
pain if one has to fix value conflicts across nodes as they'd usually
require extra tweaks with the sequence definitions, especially if it
blocks applications in the middle of the night. Sequence AMs offer
more control on that. For example, snowflake IDs can rely on a GUC to
set a specific machine ID to force some of the bits of a 64-bit
integer to be the same for a single node in an active-active
deployment, ensuring that any value computed across *all* the nodes of
a cluster are always unique, while being maintained behind a sequence
object in-core. (I can post a module to demonstrate that based on the
sequence AM APIs, just wait a min.. Having more than a test module
and/or a contrib is a separate discussion.)

By the way, patches 0001 to 0004 are just refactoring pieces.
Particularly, 0001 redesigns pg_sequence_last_value() to work across
the board for upgrades and dumps, while avoiding a scan of the
sequence "heap" relation in pg_dump. These are improvements for the
core code in any case.

[1]: https://github.com/pgEdge/snowflake
[2]: /messages/by-id/TY3PR01MB988983D23E4F1DA10567BC5BF5B9A@TY3PR01MB9889.jpnprd01.prod.outlook.com -- Michael
--
Michael

#6Peter Smith
smithpb2250@gmail.com
In reply to: Michael Paquier (#2)
Re: Sequence Access Methods, round two

2024-01 Commitfest.

Hi, This patch has a CF status of "Needs Review" [1]https://commitfest.postgresql.org/46/4677/, but it seems
there were CFbot test failures last time it was run [2]https://cirrus-ci.com/task/5576959615303680. Please have a
look and post an updated version if necessary.

======
[1]: https://commitfest.postgresql.org/46/4677/
[2]: https://cirrus-ci.com/task/5576959615303680

Kind Regards,
Peter Smith.

#7Michael Paquier
michael@paquier.xyz
In reply to: Peter Smith (#6)
10 attachment(s)
Re: Sequence Access Methods, round two

On Mon, Jan 22, 2024 at 05:03:16PM +1100, Peter Smith wrote:

Hi, This patch has a CF status of "Needs Review" [1], but it seems
there were CFbot test failures last time it was run [2]. Please have a
look and post an updated version if necessary.

Indeed. This is conflicting with the new gist_stratnum_identity on
OID 8047, so switched to 8048. There was a second one in
src/test/modules/meson.build. Attached is a rebased patch set.
--
Michael

Attachments:

v3-0001-Switch-pg_sequence_last_value-to-report-a-tuple-a.patchtext/x-diff; charset=us-asciiDownload
From e69db93d04c9e3f35c2c3d4da8bf2434c9d8f63b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 09:37:24 +0900
Subject: [PATCH v3 01/10] Switch pg_sequence_last_value() to report a tuple
 and use it in pg_dump

This commit switches pg_sequence_last_value() to report a tuple made of
(last_value,is_called) that can be directly be used for the arguments of
setval() in a sequence.

Going forward with PostgreSQL 17, pg_dump and pg_sequences make use of
it instead of scanning the heap table assumed to always exist for a
sequence.

Note: this requires a catversion bump.
---
 src/include/catalog/pg_proc.dat      |  6 ++++--
 src/backend/catalog/system_views.sql |  6 +++++-
 src/backend/commands/sequence.c      | 19 +++++++++++++------
 src/bin/pg_dump/pg_dump.c            | 16 +++++++++++++---
 src/test/regress/expected/rules.out  |  7 ++++++-
 5 files changed, 41 insertions(+), 13 deletions(-)

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index ad74e07dbb..47e871533b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3325,9 +3325,11 @@
   proargmodes => '{i,o,o,o,o,o,o,o}',
   proargnames => '{sequence_oid,start_value,minimum_value,maximum_value,increment,cycle_option,cache_size,data_type}',
   prosrc => 'pg_sequence_parameters' },
-{ oid => '4032', descr => 'sequence last value',
+{ oid => '4032', descr => 'sequence last value data',
   proname => 'pg_sequence_last_value', provolatile => 'v', proparallel => 'u',
-  prorettype => 'int8', proargtypes => 'regclass',
+  prorettype => 'record', proargtypes => 'regclass',
+  proallargtypes => '{regclass,bool,int8}', proargmodes => '{i,o,o}',
+  proargnames => '{seqname,is_called,last_value}',
   prosrc => 'pg_sequence_last_value' },
 
 { oid => '275', descr => 'return the next oid for a system table',
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e43e36f5ac..3de1e23aee 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -178,7 +178,11 @@ CREATE VIEW pg_sequences AS
         S.seqcache AS cache_size,
         CASE
             WHEN has_sequence_privilege(C.oid, 'SELECT,USAGE'::text)
-                THEN pg_sequence_last_value(C.oid)
+                THEN (SELECT
+                          CASE WHEN sl.is_called
+                              THEN sl.last_value ELSE NULL
+                          END
+                      FROM pg_sequence_last_value(C.oid) sl)
             ELSE NULL
         END AS last_value
     FROM pg_sequence S JOIN pg_class C ON (C.oid = S.seqrelid)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index f7803744a5..d0b3b0b6a6 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1779,14 +1779,22 @@ pg_sequence_parameters(PG_FUNCTION_ARGS)
 Datum
 pg_sequence_last_value(PG_FUNCTION_ARGS)
 {
+#define PG_SEQUENCE_LAST_VALUE_COLS		2
 	Oid			relid = PG_GETARG_OID(0);
+	Datum		values[PG_SEQUENCE_LAST_VALUE_COLS] = {0};
+	bool		nulls[PG_SEQUENCE_LAST_VALUE_COLS] = {0};
 	SeqTable	elm;
 	Relation	seqrel;
+	TupleDesc	tupdesc;
 	Buffer		buf;
 	HeapTupleData seqtuple;
 	Form_pg_sequence_data seq;
 	bool		is_called;
-	int64		result;
+	int64		last_value;
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	/* open and lock sequence */
 	init_sequence(relid, &elm, &seqrel);
@@ -1800,15 +1808,14 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	seq = read_seq_tuple(seqrel, &buf, &seqtuple);
 
 	is_called = seq->is_called;
-	result = seq->last_value;
+	last_value = seq->last_value;
 
 	UnlockReleaseBuffer(buf);
 	relation_close(seqrel, NoLock);
 
-	if (is_called)
-		PG_RETURN_INT64(result);
-	else
-		PG_RETURN_NULL();
+	values[0] = BoolGetDatum(is_called);
+	values[1] = Int64GetDatum(last_value);
+	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
 }
 
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index bc20a025ce..7347ca6ddb 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -17651,9 +17651,19 @@ dumpSequenceData(Archive *fout, const TableDataInfo *tdinfo)
 	bool		called;
 	PQExpBuffer query = createPQExpBuffer();
 
-	appendPQExpBuffer(query,
-					  "SELECT last_value, is_called FROM %s",
-					  fmtQualifiedDumpable(tbinfo));
+	/*
+	 * In versions 17 and up, pg_sequence_last_value() has been switched to
+	 * return a tuple with last_value and is_called.
+	 */
+	if (fout->remoteVersion >= 170000)
+		appendPQExpBuffer(query,
+						  "SELECT last_value, is_called "
+						  "FROM pg_sequence_last_value('%s')",
+						  fmtQualifiedDumpable(tbinfo));
+	else
+		appendPQExpBuffer(query,
+						  "SELECT last_value, is_called FROM %s",
+						  fmtQualifiedDumpable(tbinfo));
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 55f2e95352..7c338e6c6b 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1696,7 +1696,12 @@ pg_sequences| SELECT n.nspname AS schemaname,
     s.seqcycle AS cycle,
     s.seqcache AS cache_size,
         CASE
-            WHEN has_sequence_privilege(c.oid, 'SELECT,USAGE'::text) THEN pg_sequence_last_value((c.oid)::regclass)
+            WHEN has_sequence_privilege(c.oid, 'SELECT,USAGE'::text) THEN ( SELECT
+                    CASE
+                        WHEN sl.is_called THEN sl.last_value
+                        ELSE NULL::bigint
+                    END AS "case"
+               FROM pg_sequence_last_value((c.oid)::regclass) sl(is_called, last_value))
             ELSE NULL::bigint
         END AS last_value
    FROM ((pg_sequence s
-- 
2.43.0

v3-0002-Introduce-sequence_-access-functions.patchtext/x-diff; charset=us-asciiDownload
From bdf518e61f895822a6297aec1e8f3b2c45b074ac Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 10:02:07 +0900
Subject: [PATCH v3 02/10] Introduce sequence_*() access functions

Similarly to tables and indexes, these functions are only able to open
relations with a sequence relkind, which is useful to make a distinction
with the other types that can have AMs.
---
 src/include/access/sequence.h           | 23 ++++++++
 src/backend/access/Makefile             |  2 +-
 src/backend/access/meson.build          |  1 +
 src/backend/access/sequence/Makefile    | 17 ++++++
 src/backend/access/sequence/meson.build |  5 ++
 src/backend/access/sequence/sequence.c  | 78 +++++++++++++++++++++++++
 src/backend/commands/sequence.c         | 31 +++++-----
 src/test/regress/expected/sequence.out  |  3 +-
 8 files changed, 140 insertions(+), 20 deletions(-)
 create mode 100644 src/include/access/sequence.h
 create mode 100644 src/backend/access/sequence/Makefile
 create mode 100644 src/backend/access/sequence/meson.build
 create mode 100644 src/backend/access/sequence/sequence.c

diff --git a/src/include/access/sequence.h b/src/include/access/sequence.h
new file mode 100644
index 0000000000..5d2a3d8a71
--- /dev/null
+++ b/src/include/access/sequence.h
@@ -0,0 +1,23 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequence.h
+ *	  Generic routines for sequence related code.
+ *
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/sequence/sequence.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ACCESS_SEQUENCE_H
+#define ACCESS_SEQUENCE_H
+
+#include "storage/lockdefs.h"
+#include "utils/relcache.h"
+
+extern Relation sequence_open(Oid relationId, LOCKMODE lockmode);
+extern void sequence_close(Relation relation, LOCKMODE lockmode);
+
+#endif							/* ACCESS_SEQUENCE_H */
diff --git a/src/backend/access/Makefile b/src/backend/access/Makefile
index 0880e0a8bb..1932d11d15 100644
--- a/src/backend/access/Makefile
+++ b/src/backend/access/Makefile
@@ -9,6 +9,6 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 SUBDIRS	    = brin common gin gist hash heap index nbtree rmgrdesc spgist \
-			  table tablesample transam
+			  sequence table tablesample transam
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/meson.build b/src/backend/access/meson.build
index 621691ef34..62a371db7f 100644
--- a/src/backend/access/meson.build
+++ b/src/backend/access/meson.build
@@ -9,6 +9,7 @@ subdir('heap')
 subdir('index')
 subdir('nbtree')
 subdir('rmgrdesc')
+subdir('sequence')
 subdir('spgist')
 subdir('table')
 subdir('tablesample')
diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile
new file mode 100644
index 0000000000..9f9d31f542
--- /dev/null
+++ b/src/backend/access/sequence/Makefile
@@ -0,0 +1,17 @@
+#-------------------------------------------------------------------------
+#
+# Makefile
+#    Makefile for access/sequence
+#
+# IDENTIFICATION
+#    src/backend/access/sequence/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/access/sequence
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = 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
new file mode 100644
index 0000000000..1840a913bc
--- /dev/null
+++ b/src/backend/access/sequence/meson.build
@@ -0,0 +1,5 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+backend_sources += files(
+  'sequence.c',
+)
diff --git a/src/backend/access/sequence/sequence.c b/src/backend/access/sequence/sequence.c
new file mode 100644
index 0000000000..a5b9fccb50
--- /dev/null
+++ b/src/backend/access/sequence/sequence.c
@@ -0,0 +1,78 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequence.c
+ *	  Generic routines for sequence related code.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/sequence/sequence.c
+ *
+ *
+ * NOTES
+ *	  This file contains sequence_ routines that implement access to sequences
+ *	  (in contrast to other relation types like indexes).
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/relation.h"
+#include "access/sequence.h"
+#include "storage/lmgr.h"
+
+static inline void validate_relation_kind(Relation r);
+
+/* ----------------
+ *		sequence_open - open a sequence relation by relation OID
+ *
+ *		This is essentially relation_open plus check that the relation
+ *		is a sequence.
+ * ----------------
+ */
+Relation
+sequence_open(Oid relationId, LOCKMODE lockmode)
+{
+	Relation	r;
+
+	r = relation_open(relationId, lockmode);
+
+	validate_relation_kind(r);
+
+	return r;
+}
+
+/* ----------------
+ *		sequence_close - close a sequence
+ *
+ *		If lockmode is not "NoLock", we then release the specified lock.
+ *
+ *		Note that it is often sensible to hold a lock beyond relation_close;
+ *		in that case, the lock is released automatically at xact end.
+ *		----------------
+ */
+void
+sequence_close(Relation relation, LOCKMODE lockmode)
+{
+	relation_close(relation, lockmode);
+}
+
+/* ----------------
+ *		validate_relation_kind - check the relation's kind
+ *
+ *		Make sure relkind is from an index
+ * ----------------
+ */
+static inline void
+validate_relation_kind(Relation r)
+{
+	if (r->rd_rel->relkind != RELKIND_SEQUENCE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot open relation \"%s\"",
+						RelationGetRelationName(r)),
+				 errdetail_relkind_not_supported(r->rd_rel->relkind)));
+}
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index d0b3b0b6a6..c72e8a7da3 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -18,6 +18,7 @@
 #include "access/htup_details.h"
 #include "access/multixact.h"
 #include "access/relation.h"
+#include "access/sequence.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -208,7 +209,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	seqoid = address.objectId;
 	Assert(seqoid != InvalidOid);
 
-	rel = table_open(seqoid, AccessExclusiveLock);
+	rel = sequence_open(seqoid, AccessExclusiveLock);
 	tupDesc = RelationGetDescr(rel);
 
 	/* now initialize the sequence's data */
@@ -219,7 +220,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	if (owned_by)
 		process_owned_by(rel, owned_by, seq->for_identity);
 
-	table_close(rel, NoLock);
+	sequence_close(rel, NoLock);
 
 	/* fill in pg_sequence */
 	rel = table_open(SequenceRelationId, RowExclusiveLock);
@@ -324,7 +325,7 @@ ResetSequence(Oid seq_relid)
 	/* Note that we do not change the currval() state */
 	elm->cached = elm->last;
 
-	relation_close(seq_rel, NoLock);
+	sequence_close(seq_rel, NoLock);
 }
 
 /*
@@ -531,7 +532,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	ObjectAddressSet(address, RelationRelationId, relid);
 
 	table_close(rel, RowExclusiveLock);
-	relation_close(seqrel, NoLock);
+	sequence_close(seqrel, NoLock);
 
 	return address;
 }
@@ -555,7 +556,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence)
 	fill_seq_with_data(seqrel, &seqdatatuple);
 	UnlockReleaseBuffer(buf);
 
-	relation_close(seqrel, NoLock);
+	sequence_close(seqrel, NoLock);
 }
 
 void
@@ -662,7 +663,7 @@ nextval_internal(Oid relid, bool check_permissions)
 		Assert(elm->last_valid);
 		Assert(elm->increment != 0);
 		elm->last += elm->increment;
-		relation_close(seqrel, NoLock);
+		sequence_close(seqrel, NoLock);
 		last_used_seq = elm;
 		return elm->last;
 	}
@@ -849,7 +850,7 @@ nextval_internal(Oid relid, bool check_permissions)
 
 	UnlockReleaseBuffer(buf);
 
-	relation_close(seqrel, NoLock);
+	sequence_close(seqrel, NoLock);
 
 	return result;
 }
@@ -880,7 +881,7 @@ currval_oid(PG_FUNCTION_ARGS)
 
 	result = elm->last;
 
-	relation_close(seqrel, NoLock);
+	sequence_close(seqrel, NoLock);
 
 	PG_RETURN_INT64(result);
 }
@@ -915,7 +916,7 @@ lastval(PG_FUNCTION_ARGS)
 						RelationGetRelationName(seqrel))));
 
 	result = last_used_seq->last;
-	relation_close(seqrel, NoLock);
+	sequence_close(seqrel, NoLock);
 
 	PG_RETURN_INT64(result);
 }
@@ -1030,7 +1031,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 
 	UnlockReleaseBuffer(buf);
 
-	relation_close(seqrel, NoLock);
+	sequence_close(seqrel, NoLock);
 }
 
 /*
@@ -1095,7 +1096,7 @@ lock_and_open_sequence(SeqTable seq)
 	}
 
 	/* We now know we have the lock, and can safely open the rel */
-	return relation_open(seq->relid, NoLock);
+	return sequence_open(seq->relid, NoLock);
 }
 
 /*
@@ -1152,12 +1153,6 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
 	 */
 	seqrel = lock_and_open_sequence(elm);
 
-	if (seqrel->rd_rel->relkind != RELKIND_SEQUENCE)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a sequence",
-						RelationGetRelationName(seqrel))));
-
 	/*
 	 * If the sequence has been transactionally replaced since we last saw it,
 	 * discard any cached-but-unissued values.  We do not touch the currval()
@@ -1811,7 +1806,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	last_value = seq->last_value;
 
 	UnlockReleaseBuffer(buf);
-	relation_close(seqrel, NoLock);
+	sequence_close(seqrel, NoLock);
 
 	values[0] = BoolGetDatum(is_called);
 	values[1] = Int64GetDatum(last_value);
diff --git a/src/test/regress/expected/sequence.out b/src/test/regress/expected/sequence.out
index 7cb2f7cc02..2b47b7796b 100644
--- a/src/test/regress/expected/sequence.out
+++ b/src/test/regress/expected/sequence.out
@@ -313,7 +313,8 @@ ALTER SEQUENCE IF EXISTS sequence_test2 RESTART WITH 24
   INCREMENT BY 4 MAXVALUE 36 MINVALUE 5 CYCLE;
 NOTICE:  relation "sequence_test2" does not exist, skipping
 ALTER SEQUENCE serialTest1 CYCLE;  -- error, not a sequence
-ERROR:  "serialtest1" is not a sequence
+ERROR:  cannot open relation "serialtest1"
+DETAIL:  This operation is not supported for tables.
 CREATE SEQUENCE sequence_test2 START WITH 32;
 CREATE SEQUENCE sequence_test4 INCREMENT BY -1;
 SELECT nextval('sequence_test2');
-- 
2.43.0

v3-0003-Group-more-closely-local-sequence-cache-updates.patchtext/x-diff; charset=us-asciiDownload
From 8004123f1947901d9d66e6bb7744f0a4648e8392 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 10:08:17 +0900
Subject: [PATCH v3 03/10] Group more closely local sequence cache updates

Previously, some updates of the informations for SeqTable entries was
mixed in the middle of computations.  Grouping them makes the code
easier to follow and split later on.
---
 src/backend/commands/sequence.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index c72e8a7da3..f122e943fb 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -489,10 +489,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 				seqform, newdataform,
 				&need_seq_rewrite, &owned_by);
 
-	/* Clear local cache so that we don't think we have cached numbers */
-	/* Note that we do not change the currval() state */
-	elm->cached = elm->last;
-
 	/* If needed, rewrite the sequence relation itself */
 	if (need_seq_rewrite)
 	{
@@ -520,6 +516,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		fill_seq_with_data(seqrel, newdatatuple);
 	}
 
+	/* Clear local cache so that we don't think we have cached numbers */
+	/* Note that we do not change the currval() state */
+	elm->cached = elm->last;
+
 	/* process OWNED BY if given */
 	if (owned_by)
 		process_owned_by(seqrel, owned_by, stmt->for_identity);
@@ -683,7 +683,6 @@ nextval_internal(Oid relid, bool check_permissions)
 	seq = read_seq_tuple(seqrel, &buf, &seqdatatuple);
 	page = BufferGetPage(buf);
 
-	elm->increment = incby;
 	last = next = result = seq->last_value;
 	fetch = cache;
 	log = seq->log_cnt;
@@ -781,6 +780,7 @@ nextval_internal(Oid relid, bool check_permissions)
 	Assert(log >= 0);
 
 	/* save info in local cache */
+	elm->increment = incby;
 	elm->last = result;			/* last returned number */
 	elm->cached = last;			/* last fetched number */
 	elm->last_valid = true;
-- 
2.43.0

v3-0004-Remove-FormData_pg_sequence_data-from-init_params.patchtext/x-diff; charset=us-asciiDownload
From 86ee1aeec8a7b8f97f2044a6ae739c4e184f69c8 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:00:45 +0900
Subject: [PATCH v3 04/10] Remove FormData_pg_sequence_data from
 init_params()/sequence.c

init_params() sets up "last_value" and "is_called" for a sequence, based
on the sequence properties in pg_sequences.  This simplifies the logic
around log_cnt, which is reset to 0 when the metadata of a sequence is
expected to start from afresh when its properties are updated.
---
 src/backend/commands/sequence.c | 81 ++++++++++++++++++++-------------
 1 file changed, 49 insertions(+), 32 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index f122e943fb..758d32dd45 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -106,7 +106,9 @@ static Form_pg_sequence_data read_seq_tuple(Relation rel,
 static void init_params(ParseState *pstate, List *options, bool for_identity,
 						bool isInit,
 						Form_pg_sequence seqform,
-						Form_pg_sequence_data seqdataform,
+						int64 *last_value,
+						bool *reset_state,
+						bool *is_called,
 						bool *need_seq_rewrite,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
@@ -121,7 +123,9 @@ ObjectAddress
 DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 {
 	FormData_pg_sequence seqform;
-	FormData_pg_sequence_data seqdataform;
+	int64		last_value;
+	bool		reset_state;
+	bool		is_called;
 	bool		need_seq_rewrite;
 	List	   *owned_by;
 	CreateStmt *stmt = makeNode(CreateStmt);
@@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* Check and set all option values */
 	init_params(pstate, seq->options, seq->for_identity, true,
-				&seqform, &seqdataform,
+				&seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/*
@@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 		{
 			case SEQ_COL_LASTVAL:
 				coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
-				value[i - 1] = Int64GetDatumFast(seqdataform.last_value);
+				value[i - 1] = Int64GetDatumFast(last_value);
 				break;
 			case SEQ_COL_LOG:
 				coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
@@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	ObjectAddress address;
 	Relation	rel;
 	HeapTuple	seqtuple;
+	bool		reset_state = false;
+	bool		is_called;
+	int64		last_value;
 	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
@@ -481,12 +488,14 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	/* 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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
-				seqform, newdataform,
+				seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/* If needed, rewrite the sequence relation itself */
@@ -513,6 +522,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * 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);
 	}
 
@@ -1229,17 +1242,19 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 /*
  * init_params: process the options list of CREATE or ALTER SEQUENCE, and
  * store the values into appropriate fields of seqform, for changes that go
- * into the pg_sequence catalog, and fields of seqdataform for changes to the
- * sequence relation itself.  Set *need_seq_rewrite to true if we changed any
- * parameters that require rewriting the sequence's relation (interesting for
- * ALTER SEQUENCE).  Also set *owned_by to any OWNED BY option, or to NIL if
- * there is none.
+ * into the pg_sequence catalog, and fields for changes to the sequence
+ * relation itself (is_called, last_value or any state it may hold).  Set
+ * *need_seq_rewrite to true if we changed any parameters that require
+ * rewriting the sequence's relation (interesting for ALTER SEQUENCE).  Also
+ * set *owned_by to any OWNED BY option, or to NIL if there is none.  Set
+ * *reset_state if the internal state of the sequence needs to change on a
+ * follow-up nextval().
  *
  * If isInit is true, fill any unspecified options with default values;
  * otherwise, do not change existing options that aren't explicitly overridden.
  *
  * Note: we force a sequence rewrite whenever we change parameters that affect
- * generation of future sequence values, even if the seqdataform per se is not
+ * generation of future sequence values, even if the metadata per se is not
  * changed.  This allows ALTER SEQUENCE to behave transactionally.  Currently,
  * the only option that doesn't cause that is OWNED BY.  It's *necessary* for
  * ALTER SEQUENCE OWNED BY to not rewrite the sequence, because that would
@@ -1250,7 +1265,9 @@ static void
 init_params(ParseState *pstate, List *options, bool for_identity,
 			bool isInit,
 			Form_pg_sequence seqform,
-			Form_pg_sequence_data seqdataform,
+			int64 *last_value,
+			bool *reset_state,
+			bool *is_called,
 			bool *need_seq_rewrite,
 			List **owned_by)
 {
@@ -1353,11 +1370,11 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	}
 
 	/*
-	 * We must reset log_cnt when isInit or when changing any parameters that
-	 * would affect future nextval allocations.
+	 * We must reset the state when isInit or when changing any parameters
+	 * that would affect future nextval allocations.
 	 */
 	if (isInit)
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 
 	/* AS type */
 	if (as_type != NULL)
@@ -1406,7 +1423,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("INCREMENT must not be zero")));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1418,7 +1435,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	{
 		seqform->seqcycle = boolVal(is_cycled->arg);
 		Assert(BoolIsValid(seqform->seqcycle));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1429,7 +1446,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (max_value != NULL && max_value->arg)
 	{
 		seqform->seqmax = defGetInt64(max_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || max_value != NULL || reset_max_value)
 	{
@@ -1445,7 +1462,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmax = -1;	/* descending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate maximum value.  No need to check INT8 as seqmax is an int64 */
@@ -1461,7 +1478,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (min_value != NULL && min_value->arg)
 	{
 		seqform->seqmin = defGetInt64(min_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || min_value != NULL || reset_min_value)
 	{
@@ -1477,7 +1494,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmin = 1;	/* ascending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate minimum value.  No need to check INT8 as seqmin is an int64 */
@@ -1528,30 +1545,30 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (restart_value != NULL)
 	{
 		if (restart_value->arg != NULL)
-			seqdataform->last_value = defGetInt64(restart_value);
+			*last_value = defGetInt64(restart_value);
 		else
-			seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
-		seqdataform->log_cnt = 0;
+			*last_value = seqform->seqstart;
+		*is_called = false;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-		seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
+		*last_value = seqform->seqstart;
+		*is_called = false;
 	}
 
 	/* crosscheck RESTART (or current value, if changing MIN/MAX) */
-	if (seqdataform->last_value < seqform->seqmin)
+	if (*last_value < seqform->seqmin)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be less than MINVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmin)));
-	if (seqdataform->last_value > seqform->seqmax)
+	if (*last_value > seqform->seqmax)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be greater than MAXVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmax)));
 
 	/* CACHE */
@@ -1563,7 +1580,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("CACHE (%lld) must be greater than zero",
 							(long long) seqform->seqcache)));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-- 
2.43.0

v3-0005-Integrate-addition-of-attributes-for-sequences-wi.patchtext/x-diff; charset=us-asciiDownload
From 683d9d4c427e1d89f80b3324f55a7fff2712d97f Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v3 05/10] 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               | 29 +++++++++++++++++--
 src/backend/commands/tablecmds.c              | 10 +++++++
 src/backend/tcop/utility.c                    |  4 +++
 .../test_ddl_deparse/expected/alter_table.out | 10 +++++--
 .../expected/create_sequence_1.out            |  5 +++-
 .../expected/create_table.out                 | 15 ++++++++--
 .../test_ddl_deparse/test_ddl_deparse.c       |  3 ++
 8 files changed, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b3181f34ae..02654302da 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2186,6 +2186,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 758d32dd45..2d9faf5dd6 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -136,6 +136,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;
@@ -174,7 +177,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;
@@ -198,7 +200,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;
@@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 2a56a4357c..c65e653435 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4510,6 +4510,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 */
@@ -4805,6 +4806,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,
@@ -5228,6 +5236,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,
@@ -6383,6 +6392,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 8de821f960..fd60678add 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1671,7 +1671,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 b5e71af9aa..7ebd85200f 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
@@ -71,7 +74,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_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
index 5837ea484e..310ce5a6ba 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.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 75b62aff4d..69e54358ee 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 alter table, tag ALTER TABLE
 NOTICE:    subcommand: type SET ATTNOTNULL desc <NULL>
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 48563b2cf0..bba80a7a3d 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -114,6 +114,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.43.0

v3-0006-Move-code-for-local-sequences-to-own-file.patchtext/x-diff; charset=us-asciiDownload
From 1a363c081519640914997ef75b4fcd0d6f3abf17 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:01:55 +0900
Subject: [PATCH v3 06/10] Move code for local sequences to own file

Now that the separation between the in-core sequence computations and
the catalog layer is clean, this moves the code corresponding to the
"local" sequence AM into its own file, out of sequence.c.  The WAL
routines related to sequence are moved in it as well.
---
 src/include/access/localam.h                  |  48 ++
 src/include/access/rmgrlist.h                 |   2 +-
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 .../rmgrdesc/{seqdesc.c => localseqdesc.c}    |  18 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/local.c           | 706 ++++++++++++++++++
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 619 +--------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 12 files changed, 793 insertions(+), 611 deletions(-)
 create mode 100644 src/include/access/localam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => localseqdesc.c} (69%)
 create mode 100644 src/backend/access/sequence/local.c

diff --git a/src/include/access/localam.h b/src/include/access/localam.h
new file mode 100644
index 0000000000..5b0575dc2e
--- /dev/null
+++ b/src/include/access/localam.h
@@ -0,0 +1,48 @@
+/*-------------------------------------------------------------------------
+ *
+ * localam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/localam.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef LOCALAM_H
+#define LOCALAM_H
+
+#include "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_LOCAL_SEQ_LOG			0x00
+
+typedef struct xl_local_seq_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_local_seq_rec;
+
+extern void local_seq_redo(XLogReaderState *record);
+extern void local_seq_desc(StringInfo buf, XLogReaderState *record);
+extern const char *local_seq_identify(uint8 info);
+extern void local_seq_mask(char *page, BlockNumber blkno);
+
+/* access routines */
+extern int64 local_seq_nextval(Relation rel, int64 incby, int64 maxv,
+							   int64 minv, int64 cache, bool cycle,
+							   int64 *last);
+extern const char *local_seq_get_table_am(void);
+extern void local_seq_init(Relation rel, int64 last_value, bool is_called);
+extern void local_seq_setval(Relation rel, int64 next, bool iscalled);
+extern void local_seq_reset(Relation rel, int64 startv, bool is_called,
+							bool reset_state);
+extern void local_seq_get_state(Relation rel, int64 *last_value,
+								bool *is_called);
+extern void local_seq_change_persistence(Relation rel,
+										 char newrelpersistence);
+
+#endif							/* LOCALAM_H */
diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 78e6b908c6..46fd63ae47 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_LOCAL_SEQ_ID, "LocalSequence", local_seq_redo, local_seq_desc, local_seq_identify, NULL, NULL, local_seq_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/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile
index cd95eec37f..dff5a60e68 100644
--- a/src/backend/access/rmgrdesc/Makefile
+++ b/src/backend/access/rmgrdesc/Makefile
@@ -18,13 +18,13 @@ OBJS = \
 	gistdesc.o \
 	hashdesc.o \
 	heapdesc.o \
+	localseqdesc.o \
 	logicalmsgdesc.o \
 	mxactdesc.o \
 	nbtdesc.o \
 	relmapdesc.o \
 	replorigindesc.o \
 	rmgrdesc_utils.o \
-	seqdesc.o \
 	smgrdesc.o \
 	spgdesc.o \
 	standbydesc.o \
diff --git a/src/backend/access/rmgrdesc/seqdesc.c b/src/backend/access/rmgrdesc/localseqdesc.c
similarity index 69%
rename from src/backend/access/rmgrdesc/seqdesc.c
rename to src/backend/access/rmgrdesc/localseqdesc.c
index cf0e02ded5..17b8b71093 100644
--- a/src/backend/access/rmgrdesc/seqdesc.c
+++ b/src/backend/access/rmgrdesc/localseqdesc.c
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
- * seqdesc.c
- *	  rmgr descriptor routines for commands/sequence.c
+ * localseqdesc.c
+ *	  rmgr descriptor routines for sequence/local.c
  *
  * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -14,31 +14,31 @@
  */
 #include "postgres.h"
 
-#include "commands/sequence.h"
+#include "access/localam.h"
 
 
 void
-seq_desc(StringInfo buf, XLogReaderState *record)
+local_seq_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_local_seq_rec *xlrec = (xl_local_seq_rec *) rec;
 
-	if (info == XLOG_SEQ_LOG)
+	if (info == XLOG_LOCAL_SEQ_LOG)
 		appendStringInfo(buf, "rel %u/%u/%u",
 						 xlrec->locator.spcOid, xlrec->locator.dbOid,
 						 xlrec->locator.relNumber);
 }
 
 const char *
-seq_identify(uint8 info)
+local_seq_identify(uint8 info)
 {
 	const char *id = NULL;
 
 	switch (info & ~XLR_INFO_MASK)
 	{
-		case XLOG_SEQ_LOG:
-			id = "LOG";
+		case XLOG_LOCAL_SEQ_LOG:
+			id = "LOCAL_SEQ_LOG";
 			break;
 	}
 
diff --git a/src/backend/access/rmgrdesc/meson.build b/src/backend/access/rmgrdesc/meson.build
index e8b7a65fc7..5f2f28072b 100644
--- a/src/backend/access/rmgrdesc/meson.build
+++ b/src/backend/access/rmgrdesc/meson.build
@@ -11,13 +11,13 @@ rmgr_desc_sources = files(
   'gistdesc.c',
   'hashdesc.c',
   'heapdesc.c',
+  'localseqdesc.c',
   'logicalmsgdesc.c',
   'mxactdesc.c',
   'nbtdesc.c',
   'relmapdesc.c',
   'replorigindesc.c',
   'rmgrdesc_utils.c',
-  'seqdesc.c',
   'smgrdesc.c',
   'spgdesc.c',
   'standbydesc.c',
diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile
index 9f9d31f542..697f89905e 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = local.o sequence.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/sequence/local.c b/src/backend/access/sequence/local.c
new file mode 100644
index 0000000000..e77f25e13e
--- /dev/null
+++ b/src/backend/access/sequence/local.c
@@ -0,0 +1,706 @@
+/*-------------------------------------------------------------------------
+ *
+ * local.c
+ *	  Local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/access/sequence/local.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/localam.h"
+#include "access/multixact.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"
+
+
+/*
+ * 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 LOCAL_SEQ_LOG_VALS	32
+
+/*
+ * The "special area" of a local sequence's buffer page looks like this.
+ */
+#define LOCAL_SEQ_MAGIC	  0x1717
+
+typedef struct local_sequence_magic
+{
+	uint32		magic;
+} local_sequence_magic;
+
+/* Format of tuples stored in heap table associated to local sequences */
+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 local 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
+
+
+/*
+ * 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
+
+static Form_pg_sequence_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_sequence_data
+read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
+{
+	Page		page;
+	ItemId		lp;
+	local_sequence_magic *sm;
+	Form_pg_sequence_data seq;
+
+	*buf = ReadBuffer(rel, 0);
+	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(*buf);
+	sm = (local_sequence_magic *) PageGetSpecialPointer(page);
+
+	if (sm->magic != LOCAL_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;
+}
+
+/*
+ * 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, InvalidBackendId);
+		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;
+	local_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(local_sequence_magic));
+	sm = (local_sequence_magic *) PageGetSpecialPointer(page);
+	sm->magic = LOCAL_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, (Item) 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_local_seq_rec xlrec;
+		XLogRecPtr	recptr;
+
+		XLogBeginInsert();
+		XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
+
+		xlrec.locator = rel->rd_locator;
+
+		XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec));
+		XLogRegisterData((char *) tuple->t_data, tuple->t_len);
+
+		recptr = XLogInsert(RM_LOCAL_SEQ_ID, XLOG_LOCAL_SEQ_LOG);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buf);
+}
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+local_seq_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+local_seq_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_local_seq_rec *xlrec = (xl_local_seq_rec *) XLogRecGetData(record);
+	local_sequence_magic *sm;
+
+	if (info != XLOG_LOCAL_SEQ_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = (Page) BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	PageInit(localpage, BufferGetPageSize(buffer), sizeof(local_sequence_magic));
+	sm = (local_sequence_magic *) PageGetSpecialPointer(localpage);
+	sm->magic = LOCAL_SEQ_MAGIC;
+
+	item = (char *) xlrec + sizeof(xl_local_seq_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_local_seq_rec);
+
+	if (PageAddItem(localpage, (Item) item, itemsz,
+					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "local_seq_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
+
+/*
+ * local_seq_nextval()
+ *
+ * Allocate a new value for a local sequence, based on the sequence
+ * configuration.
+ */
+int64
+local_seq_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_sequence_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_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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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_local_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 = rel->rd_locator;
+
+		XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec));
+		XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len);
+
+		recptr = XLogInsert(RM_LOCAL_SEQ_ID, XLOG_LOCAL_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);
+
+	return result;
+}
+
+/*
+ * local_seq_get_table_am()
+ *
+ * Return the table access method used by this sequence.
+ */
+const char *
+local_seq_get_table_am(void)
+{
+	return "heap";
+}
+
+/*
+ * local_seq_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
+local_seq_init(Relation rel, int64 last_value, bool is_called)
+{
+	Datum		value[SEQ_COL_LASTCOL];
+	bool		null[SEQ_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_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(0);
+				break;
+			case SEQ_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);
+}
+
+/*
+ * local_seq_setval()
+ *
+ * Callback for setval().
+ */
+void
+local_seq_setval(Relation rel, int64 next, bool iscalled)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_sequence_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_local_seq_rec xlrec;
+		XLogRecPtr	recptr;
+		Page		page = BufferGetPage(buf);
+
+		XLogBeginInsert();
+		XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
+
+		xlrec.locator = rel->rd_locator;
+		XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec));
+		XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len);
+
+		recptr = XLogInsert(RM_LOCAL_SEQ_ID, XLOG_LOCAL_SEQ_LOG);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buf);
+}
+
+/*
+ * local_seq_reset()
+ *
+ * Perform a hard reset on the local sequence, rewriting its heap data
+ * entirely.
+ */
+void
+local_seq_reset(Relation rel, int64 startv, bool is_called, bool reset_state)
+{
+	Form_pg_sequence_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_sequence_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);
+}
+
+/*
+ * local_seq_get_state()
+ *
+ * Retrieve the state of a local sequence.
+ */
+void
+local_seq_get_state(Relation rel, int64 *last_value, bool *is_called)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_sequence_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	*last_value = seq->last_value;
+	*is_called = seq->is_called;
+
+	UnlockReleaseBuffer(buf);
+}
+
+/*
+ * local_seq_change_persistence()
+ *
+ * Persistence change for the local sequence Relation.
+ */
+void
+local_seq_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/sequence/meson.build b/src/backend/access/sequence/meson.build
index 1840a913bc..b271956232 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,6 @@
 # Copyright (c) 2022-2023, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'local.c',
   'sequence.c',
 )
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 7d67eda5f7..c3f9acb064 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -15,6 +15,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/localam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 2d9faf5dd6..f69ee063b2 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/localam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -50,23 +51,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -96,13 +80,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,
@@ -134,14 +114,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
@@ -174,35 +148,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;
@@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	/* now initialize the sequence table structure and its data */
+	local_seq_init(rel, last_value, is_called);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -292,10 +222,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;
@@ -306,7 +232,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))
@@ -315,40 +240,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. */
+	local_seq_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 */
@@ -357,106 +250,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, InvalidBackendId);
-		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, (Item) 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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) tuple->t_data, tuple->t_len);
-
-		recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	UnlockReleaseBuffer(buf);
-}
-
 /*
  * AlterSequence
  *
@@ -468,10 +261,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;
@@ -480,7 +270,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	bool		reset_state = false;
 	bool		is_called;
 	int64		last_value;
-	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
 	relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -507,16 +296,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 */
+	local_seq_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -526,32 +307,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);
+		local_seq_reset(seqrel, last_value, is_called, reset_state);
 	}
 
 	/* Clear local cache so that we don't think we have cached numbers */
@@ -580,8 +339,6 @@ SequenceChangePersistence(Oid relid, char newrelpersistence)
 {
 	SeqTable	elm;
 	Relation	seqrel;
-	Buffer		buf;
-	HeapTupleData seqdatatuple;
 
 	init_sequence(relid, &elm, &seqrel);
 
@@ -589,10 +346,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);
+	local_seq_change_persistence(seqrel, newrelpersistence);
 
 	sequence_close(seqrel, NoLock);
 }
@@ -655,24 +409,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);
@@ -717,105 +462,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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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 = local_seq_nextval(seqrel, incby, maxv, minv, cache, cycle,
+							   &last);
 
 	/* save info in local cache */
 	elm->increment = incby;
@@ -825,69 +474,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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) 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;
@@ -977,9 +563,6 @@ do_setval(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,
@@ -1013,9 +596,6 @@ do_setval(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),
@@ -1037,37 +617,8 @@ do_setval(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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) 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 */
+	local_seq_setval(seqrel, next, iscalled);
 
 	sequence_close(seqrel, NoLock);
 }
@@ -1208,62 +759,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
@@ -1823,9 +1318,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	SeqTable	elm;
 	Relation	seqrel;
 	TupleDesc	tupdesc;
-	Buffer		buf;
-	HeapTupleData seqtuple;
-	Form_pg_sequence_data seq;
 	bool		is_called;
 	int64		last_value;
 
@@ -1842,12 +1334,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 				 errmsg("permission denied for sequence %s",
 						RelationGetRelationName(seqrel))));
 
-	seq = read_seq_tuple(seqrel, &buf, &seqtuple);
-
-	is_called = seq->is_called;
-	last_value = seq->last_value;
-
-	UnlockReleaseBuffer(buf);
+	local_seq_get_state(seqrel, &last_value, &is_called);
 	sequence_close(seqrel, NoLock);
 
 	values[0] = BoolGetDatum(is_called);
@@ -1855,57 +1342,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
 }
 
-
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = (Page) BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, (Item) item, itemsz,
-					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1920,14 +1356,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c76..0f45509f2c 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/localseqdesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index 6b8c17bb4c..ff09335607 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/localam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
-- 
2.43.0

v3-0007-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From b82d846901fc29c5170cbfb459ae52eb83f597a9 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 22 Jan 2024 15:24:38 +0900
Subject: [PATCH v3 07/10] Sequence access methods - backend support

The "local" sequence AM is now plugged in as a handler in the relcache,
and a set of callbacks in sequenceam.h.
---
 src/include/access/localam.h                  |  15 --
 src/include/access/sequenceam.h               | 188 ++++++++++++++++++
 src/include/catalog/pg_am.dat                 |   3 +
 src/include/catalog/pg_am.h                   |   1 +
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/local.c           |  69 ++++---
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/sequence.c        |   3 +-
 src/backend/access/sequence/sequenceamapi.c   | 145 ++++++++++++++
 src/backend/catalog/heap.c                    |   3 +-
 src/backend/commands/amcmds.c                 |  16 ++
 src/backend/commands/sequence.c               |  21 +-
 src/backend/commands/tablecmds.c              |  12 +-
 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            |  87 ++++++--
 src/backend/utils/misc/guc_tables.c           |  12 ++
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 src/bin/psql/describe.c                       |   2 +
 src/bin/psql/tab-complete.c                   |   4 +-
 src/test/regress/expected/create_am.out       |  33 ++-
 src/test/regress/expected/opr_sanity.out      |  12 ++
 src/test/regress/expected/psql.out            |  64 +++---
 src/test/regress/sql/create_am.sql            |  24 ++-
 src/test/regress/sql/opr_sanity.sql           |  10 +
 src/tools/pgindent/typedefs.list              |   5 +-
 38 files changed, 661 insertions(+), 155 deletions(-)
 create mode 100644 src/include/access/sequenceam.h
 create mode 100644 src/backend/access/sequence/sequenceamapi.c

diff --git a/src/include/access/localam.h b/src/include/access/localam.h
index 5b0575dc2e..7afc0a9636 100644
--- a/src/include/access/localam.h
+++ b/src/include/access/localam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_LOCAL_SEQ_LOG			0x00
@@ -31,18 +30,4 @@ extern void local_seq_desc(StringInfo buf, XLogReaderState *record);
 extern const char *local_seq_identify(uint8 info);
 extern void local_seq_mask(char *page, BlockNumber blkno);
 
-/* access routines */
-extern int64 local_seq_nextval(Relation rel, int64 incby, int64 maxv,
-							   int64 minv, int64 cache, bool cycle,
-							   int64 *last);
-extern const char *local_seq_get_table_am(void);
-extern void local_seq_init(Relation rel, int64 last_value, bool is_called);
-extern void local_seq_setval(Relation rel, int64 next, bool iscalled);
-extern void local_seq_reset(Relation rel, int64 startv, bool is_called,
-							bool reset_state);
-extern void local_seq_get_state(Relation rel, int64 *last_value,
-								bool *is_called);
-extern void local_seq_change_persistence(Relation rel,
-										 char newrelpersistence);
-
 #endif							/* LOCALAM_H */
diff --git a/src/include/access/sequenceam.h b/src/include/access/sequenceam.h
new file mode 100644
index 0000000000..f0f36c6c7e
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,188 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2023, 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 "local"
+
+/* 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.
+	 */
+	void		(*get_state) (Relation rel, int64 *last_value, bool *is_called);
+
+	/*
+	 * 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)
+{
+	rel->rd_sequenceam->get_state(rel, last_value, is_called);
+}
+
+/*
+ * 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);
+
+/* ----------------------------------------------------------------------------
+ * Functions in local.c
+ * ----------------------------------------------------------------------------
+ */
+
+extern const SequenceAmRoutine *GetLocalSequenceAmRoutine(void);
+
+#endif							/* SEQUENCEAM_H */
diff --git a/src/include/catalog/pg_am.dat b/src/include/catalog/pg_am.dat
index db87490282..3b31f9df59 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 => '8048', oid_symbol => 'LOCAL_SEQUENCE_AM_OID',
+  descr => 'local sequence access method',
+  amname => 'local', amhandler => '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 e4bb61fc72..166c10d784 100644
--- a/src/include/catalog/pg_am.h
+++ b/src/include/catalog/pg_am.h
@@ -56,6 +56,7 @@ DECLARE_UNIQUE_INDEX_PKEY(pg_am_oid_index, 2652, AmOidIndexId, pg_am, btree(oid
  * 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_proc.dat b/src/include/catalog/pg_proc.dat
index 47e871533b..2f374b0608 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 => 'local_sequenceam_handler', provolatile => 'v',
+  prorettype => 'sequence_am_handler', proargtypes => 'internal',
+  prosrc => 'local_sequenceam_handler' },
+
 # Index access method handlers
 { oid => '330', descr => 'btree index access method handler',
   proname => 'bthandler', provolatile => 'v', prorettype => 'index_am_handler',
@@ -7613,6 +7619,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 d29194da31..8f00b322ec 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -626,6 +626,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 29c511e319..39d2cae422 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -141,6 +141,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 e88cbee3b5..9b22b2f248 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index b665e55b65..774ae2652c 100644
--- a/src/include/nodes/meson.build
+++ b/src/include/nodes/meson.build
@@ -9,6 +9,7 @@ node_support_input_i = [
   '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/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 02654302da..8a04b7bb48 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2973,6 +2973,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 5300c44f3b..b9d3b64626 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -53,6 +53,8 @@ extern bool check_debug_io_direct(char **newval, void **extra, GucSource source)
 extern void assign_debug_io_direct(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 a584b1ddff..2188dfebbb 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -187,6 +187,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 697f89905e..b89a7e0526 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = local.o sequence.o
+OBJS = local.o sequence.o sequenceamapi.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/sequence/local.c b/src/backend/access/sequence/local.c
index e77f25e13e..2d20abc58e 100644
--- a/src/backend/access/sequence/local.c
+++ b/src/backend/access/sequence/local.c
@@ -18,6 +18,7 @@
 #include "access/bufmask.h"
 #include "access/localam.h"
 #include "access/multixact.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -25,6 +26,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -297,15 +299,15 @@ local_seq_redo(XLogReaderState *record)
 }
 
 /*
- * local_seq_nextval()
+ * local_nextval()
  *
  * Allocate a new value for a local sequence, based on the sequence
  * configuration.
  */
-int64
-local_seq_nextval(Relation rel, int64 incby, int64 maxv,
-				  int64 minv, int64 cache, bool cycle,
-				  int64 *last)
+static int64
+local_nextval(Relation rel, int64 incby, int64 maxv,
+			  int64 minv, int64 cache, bool cycle,
+			  int64 *last)
 {
 	int64		result;
 	int64		fetch;
@@ -485,18 +487,18 @@ local_seq_nextval(Relation rel, int64 incby, int64 maxv,
 }
 
 /*
- * local_seq_get_table_am()
+ * local_get_table_am()
  *
  * Return the table access method used by this sequence.
  */
-const char *
-local_seq_get_table_am(void)
+static const char *
+local_get_table_am(void)
 {
 	return "heap";
 }
 
 /*
- * local_seq_init()
+ * 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
@@ -504,8 +506,8 @@ local_seq_get_table_am(void)
  * inserted after the relation has been created, filling in its heap
  * table.
  */
-void
-local_seq_init(Relation rel, int64 last_value, bool is_called)
+static void
+local_init(Relation rel, int64 last_value, bool is_called)
 {
 	Datum		value[SEQ_COL_LASTCOL];
 	bool		null[SEQ_COL_LASTCOL];
@@ -567,12 +569,12 @@ local_seq_init(Relation rel, int64 last_value, bool is_called)
 }
 
 /*
- * local_seq_setval()
+ * local_setval()
  *
  * Callback for setval().
  */
-void
-local_seq_setval(Relation rel, int64 next, bool iscalled)
+static void
+local_setval(Relation rel, int64 next, bool iscalled)
 {
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
@@ -614,13 +616,13 @@ local_seq_setval(Relation rel, int64 next, bool iscalled)
 }
 
 /*
- * local_seq_reset()
+ * local_reset()
  *
  * Perform a hard reset on the local sequence, rewriting its heap data
  * entirely.
  */
-void
-local_seq_reset(Relation rel, int64 startv, bool is_called, bool reset_state)
+static void
+local_reset(Relation rel, int64 startv, bool is_called, bool reset_state)
 {
 	Form_pg_sequence_data seq;
 	Buffer		buf;
@@ -668,12 +670,12 @@ local_seq_reset(Relation rel, int64 startv, bool is_called, bool reset_state)
 }
 
 /*
- * local_seq_get_state()
+ * local_get_state()
  *
  * Retrieve the state of a local sequence.
  */
-void
-local_seq_get_state(Relation rel, int64 *last_value, bool *is_called)
+static void
+local_get_state(Relation rel, int64 *last_value, bool *is_called)
 {
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
@@ -689,12 +691,12 @@ local_seq_get_state(Relation rel, int64 *last_value, bool *is_called)
 }
 
 /*
- * local_seq_change_persistence()
+ * local_change_persistence()
  *
  * Persistence change for the local sequence Relation.
  */
-void
-local_seq_change_persistence(Relation rel, char newrelpersistence)
+static void
+local_change_persistence(Relation rel, char newrelpersistence)
 {
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
@@ -704,3 +706,24 @@ local_seq_change_persistence(Relation rel, char newrelpersistence)
 	fill_seq_with_data(rel, &seqdatatuple);
 	UnlockReleaseBuffer(buf);
 }
+
+/* ------------------------------------------------------------------------
+ * Definition of the local sequence access method.
+ * ------------------------------------------------------------------------
+ */
+static const SequenceAmRoutine local_methods = {
+	.type = T_SequenceAmRoutine,
+	.get_table_am = local_get_table_am,
+	.init = local_init,
+	.nextval = local_nextval,
+	.setval = local_setval,
+	.reset = local_reset,
+	.get_state = local_get_state,
+	.change_persistence = local_change_persistence
+};
+
+Datum
+local_sequenceam_handler(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_POINTER(&local_methods);
+}
diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build
index b271956232..feae8e5884 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -3,4 +3,5 @@
 backend_sources += files(
   'local.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/sequence.c b/src/backend/access/sequence/sequence.c
index a5b9fccb50..64e023f0b4 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 0000000000..4314b9ecb5
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2023, 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/xact.h"
+#include "access/sequenceam.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 45a71081d4..963db3e364 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1451,7 +1451,8 @@ 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.
 		 */
-		if (RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE)
+		if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE) ||
+			relkind == RELKIND_SEQUENCE)
 		{
 			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 10e386288a..bec772713e 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 f69ee063b2..fc4086ea29 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/localam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -152,6 +152,7 @@ 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;
@@ -173,7 +174,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	rel = sequence_open(seqoid, AccessExclusiveLock);
 
 	/* now initialize the sequence table structure and its data */
-	local_seq_init(rel, last_value, is_called);
+	sequence_init(rel, last_value, is_called);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -241,7 +242,7 @@ ResetSequence(Oid seq_relid)
 	ReleaseSysCache(pgstuple);
 
 	/* Sequence state is forcibly reset here. */
-	local_seq_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 */
@@ -297,7 +298,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
 
 	/* Read sequence data */
-	local_seq_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -310,7 +311,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		if (RelationNeedsWAL(seqrel))
 			GetTopTransactionId();
 
-		local_seq_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 */
@@ -346,7 +347,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence)
 	if (RelationNeedsWAL(seqrel))
 		GetTopTransactionId();
 
-	local_seq_change_persistence(seqrel, newrelpersistence);
+	sequence_change_persistence(seqrel, newrelpersistence);
 
 	sequence_close(seqrel, NoLock);
 }
@@ -463,8 +464,8 @@ nextval_internal(Oid relid, bool check_permissions)
 	ReleaseSysCache(pgstuple);
 
 	/* retrieve next value from the access method */
-	result = local_seq_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;
@@ -618,7 +619,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 		GetTopTransactionId();
 
 	/* Call the access method callback */
-	local_seq_setval(seqrel, next, iscalled);
+	sequence_setval(seqrel, next, iscalled);
 
 	sequence_close(seqrel, NoLock);
 }
@@ -1334,7 +1335,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 				 errmsg("permission denied for sequence %s",
 						RelationGetRelationName(seqrel))));
 
-	local_seq_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 	sequence_close(seqrel, NoLock);
 
 	values[0] = BoolGetDatum(is_called);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c65e653435..aa22ca5345 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -22,6 +22,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"
@@ -967,10 +968,17 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	}
 	else if (RELKIND_HAS_TABLE_AM(relkind))
 		accessMethod = default_table_access_method;
+	else if (relkind == RELKIND_SEQUENCE)
+		accessMethod = default_sequence_access_method;
 
-	/* look up the access method, verify it is for a table */
+	/* look up the access method, verify it is for a table or a sequence */
 	if (accessMethod != NULL)
-		accessMethodId = get_table_am_oid(accessMethod, false);
+	{
+		if (relkind == RELKIND_SEQUENCE)
+			accessMethodId = get_sequence_am_oid(accessMethod, false);
+		else
+			accessMethodId = get_table_am_oid(accessMethod, false);
+	}
 
 	/*
 	 * Create the relation.  Inherited defaults and constraints are passed in
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 66bbad8e6e..86b1773c0b 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -47,6 +47,7 @@ node_headers = \
 	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/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 2f0a59bc87..13b483eafe 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -59,6 +59,7 @@ my @all_input_files = qw(
   nodes/execnodes.h
   access/amapi.h
   access/sdir.h
+  access/sequenceam.h
   access/tableam.h
   access/tsmapi.h
   commands/event_trigger.h
@@ -83,6 +84,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 3460fea56b..7a1804c4d3 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
@@ -4769,23 +4770,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;
@@ -4822,6 +4826,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); }
 		;
@@ -5818,6 +5827,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 b2d1fa9d0d..3b77f0c52e 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"
@@ -461,6 +462,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
 	seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
 	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 a3a991f634..751d699937 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -372,6 +372,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 20273f0be1..9bd4fdba4b 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -35,6 +35,7 @@
 #include "access/nbtree.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"
@@ -66,6 +67,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"
@@ -301,6 +303,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);
@@ -1206,9 +1209,10 @@ 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_SEQUENCE)
+		RelationInitSequenceAccessMethod(relation);
 	else
 		Assert(relation->rd_rel->relam == InvalidOid);
 
@@ -1805,17 +1809,7 @@ 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))
+	if (IsCatalogRelation(relation))
 	{
 		/*
 		 * Avoid doing a syscache lookup for catalog tables.
@@ -1846,6 +1840,47 @@ 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;
+
+	/*
+	 * 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
  *
@@ -3687,14 +3722,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);
 
 	/*
 	 * Okay to insert into the relcache hash table.
@@ -4307,13 +4345,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);
@@ -6313,8 +6359,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);
@@ -6326,6 +6374,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_tables.c b/src/backend/utils/misc/guc_tables.c
index 7fe58518d7..10342deb67 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -29,6 +29,7 @@
 #include "access/commit_ts.h"
 #include "access/gin.h"
 #include "access/toast_compression.h"
+#include "access/sequenceam.h"
 #include "access/twophase.h"
 #include "access/xlog_internal.h"
 #include "access/xlogprefetcher.h"
@@ -4045,6 +4046,17 @@ struct config_string ConfigureNamesString[] =
 		check_default_table_access_method, NULL, NULL
 	},
 
+	{
+		{"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets the default sequence access method for new sequences."),
+			NULL,
+			GUC_IS_NAME
+		},
+		&default_sequence_access_method,
+		DEFAULT_SEQUENCE_ACCESS_METHOD,
+		check_default_sequence_access_method, NULL, NULL
+	},
+
 	{
 		{"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default tablespace to create tables and indexes in."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index da10b43dac..b48df8eb6b 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -690,6 +690,7 @@
 					#   error
 #search_path = '"$user", public'	# schema names
 #row_security = on
+#default_sequence_access_method = 'local'
 #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/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index 082cb8a589..1dd822fe44 100644
--- a/src/bin/pg_waldump/t/001_basic.pl
+++ b/src/bin/pg_waldump/t/001_basic.pl
@@ -41,7 +41,7 @@ Btree
 Hash
 Gin
 Gist
-Sequence
+LocalSequence
 SPGist
 BRIN
 CommitTs
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 37f9516320..37601c1d66 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -161,10 +161,12 @@ describeAccessMethods(const char *pattern, bool verbose)
 					  "SELECT amname AS \"%s\",\n"
 					  "  CASE amtype"
 					  " WHEN 'i' THEN '%s'"
+					  " WHEN 's' THEN '%s'"
 					  " WHEN 't' 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.c b/src/bin/psql/tab-complete.c
index ada711d02f..36b03debee 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2197,7 +2197,7 @@ psql_completion(const char *text, int start, int end)
 	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");
@@ -3217,7 +3217,7 @@ psql_completion(const char *text, int start, int end)
 	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 b50293d514..dfcc9cff49 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;
@@ -331,9 +326,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 = 'local';
 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;
@@ -356,7 +354,7 @@ ORDER BY 3, 1, 2;
  r       | heap2  | tableam_parted_1_heapx
  r       | heap   | tableam_parted_2_heapx
  p       |        | tableam_parted_heapx
- S       |        | tableam_seq_heapx
+ S       | local  | tableam_seq_heapx
  r       | heap2  | tableam_tbl_heapx
  r       | heap2  | tableam_tblas_heapx
  m       | heap2  | tableam_tblmv_heapx
@@ -388,3 +386,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 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 7610b011d6..12f48e4beb 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1929,6 +1929,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 ad02772562..4e621788f5 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4959,8 +4959,8 @@ Indexes:
 -- check printing info about access methods
 \dA
 List of access methods
-  Name  | Type  
---------+-------
+  Name  |   Type   
+--------+----------
  brin   | Index
  btree  | Index
  gin    | Index
@@ -4968,13 +4968,14 @@ List of access methods
  hash   | Index
  heap   | Table
  heap2  | Table
+ local  | Sequence
  spgist | Index
-(8 rows)
+(9 rows)
 
 \dA *
 List of access methods
-  Name  | Type  
---------+-------
+  Name  |   Type   
+--------+----------
  brin   | Index
  btree  | Index
  gin    | Index
@@ -4982,8 +4983,9 @@ List of access methods
  hash   | Index
  heap   | Table
  heap2  | Table
+ local  | Sequence
  spgist | Index
-(8 rows)
+(9 rows)
 
 \dA h*
 List of access methods
@@ -5008,32 +5010,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     | 
+ local  | Sequence | 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     | 
+ local  | Sequence | 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/sql/create_am.sql b/src/test/regress/sql/create_am.sql
index 2785ffd8bb..6b180519aa 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;
@@ -222,9 +219,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 = 'local';
 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;
@@ -257,3 +258,16 @@ CREATE TABLE i_am_a_failure() USING "btree";
 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 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 2fe7b6dcc4..1409622374 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1229,6 +1229,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/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 7e866e3c3d..d90064df11 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2502,6 +2502,7 @@ SeqScan
 SeqScanState
 SeqTable
 SeqTableData
+SequenceAmRoutine
 SerCommitSeqNo
 SerialControl
 SerialIOData
@@ -3476,6 +3477,7 @@ lineno_t
 list_sort_comparator
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 locale_t
 locate_agg_of_level_context
@@ -3740,7 +3742,6 @@ save_buffer
 scram_state
 scram_state_enum
 sem_t
-sequence_magic
 set_join_pathlist_hook_type
 set_rel_pathlist_hook_type
 shm_mq
@@ -3957,6 +3958,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
@@ -3968,7 +3970,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.43.0

v3-0008-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From 1cedf2cf0fa3d2e547e09f15c3aaf869bc66901f Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v3 08/10] 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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 61038472c5..9afca952de 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8767,6 +8767,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 bb4926b887..1dcb86468c 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -95,6 +95,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 2c107199d3..76953fee12 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -257,6 +257,7 @@ break is not needed in a wider output rendering.
   &geqo;
   &tableam;
   &indexam;
+  &sequenceam;
   &generic-wal;
   &custom-rmgr;
   &btree;
diff --git a/doc/src/sgml/ref/create_access_method.sgml b/doc/src/sgml/ref/create_access_method.sgml
index dae43dbaed..3067dc4d4d 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>SEQUENCE</literal> and
+      <literal>INDEX</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 34e9084b5c..d00bdabde0 100644
--- a/doc/src/sgml/ref/create_sequence.sgml
+++ b/doc/src/sgml/ref/create_sequence.sgml
@@ -27,6 +27,7 @@ CREATE [ { TEMPORARY | TEMP } | UNLOGGED ] SEQUENCE [ IF NOT EXISTS ] <replaceab
     [ MINVALUE <replaceable class="parameter">minvalue</replaceable> | NO MINVALUE ] [ MAXVALUE <replaceable class="parameter">maxvalue</replaceable> | NO MAXVALUE ]
     [ START [ WITH ] <replaceable class="parameter">start</replaceable> ] [ CACHE <replaceable class="parameter">cache</replaceable> ] [ [ NO ] CYCLE ]
     [ OWNED BY { <replaceable class="parameter">table_name</replaceable>.<replaceable class="parameter">column_name</replaceable> | NONE } ]
+    [ USING <replaceable class="parameter">access_method</replaceable> ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -261,6 +262,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 0000000000..a96170bfac
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.43.0

v3-0009-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From 83af9fd0a3ffac2966bd18150590021d6b20e9c5 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:56:26 +0900
Subject: [PATCH v3 09/10] 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            | 39 ++++++++++++++--
 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     | 63 ++++++++++++++++++++++----
 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, 210 insertions(+), 14 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 9ef2f2017e..a838e6490c 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -95,6 +95,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
@@ -183,6 +184,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 256d1e35a4..0899e3bea9 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -171,6 +171,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;
@@ -1118,6 +1119,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->owner = opts->owner ? pg_strdup(opts->owner) : NULL;
 	newToc->desc = pg_strdup(opts->description);
@@ -2258,6 +2260,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 */
 
@@ -2487,6 +2490,7 @@ WriteToc(ArchiveHandle *AH)
 		WriteStr(AH, te->copyStmt);
 		WriteStr(AH, te->namespace);
 		WriteStr(AH, te->tablespace);
+		WriteStr(AH, te->sequenceam);
 		WriteStr(AH, te->tableam);
 		WriteStr(AH, te->owner);
 		WriteStr(AH, "false");
@@ -2590,6 +2594,9 @@ ReadToc(ArchiveHandle *AH)
 		if (AH->version >= K_VERS_1_10)
 			te->tablespace = ReadStr(AH);
 
+		if (AH->version >= K_VERS_1_16)
+			te->sequenceam = ReadStr(AH);
+
 		if (AH->version >= K_VERS_1_14)
 			te->tableam = ReadStr(AH);
 
@@ -3226,6 +3233,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;
 
@@ -3388,6 +3398,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.
  */
@@ -3547,6 +3608,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
 	_becomeOwner(AH, te);
 	_selectOutputSchema(AH, te->namespace);
 	_selectTablespace(AH, te->tablespace);
+	_selectSequenceAccessMethod(AH, te->sequenceam);
 	_selectTableAccessMethod(AH, te->tableam);
 
 	/* Emit header comment for item */
@@ -4003,6 +4065,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);
@@ -4738,6 +4802,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -4787,6 +4852,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 917283fd34..d29a22ac40 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -68,10 +68,11 @@
 #define K_VERS_1_15 MAKE_ARCHIVE_VERSION(1, 15, 0)	/* add
 													 * compression_algorithm
 													 * in header */
+#define K_VERS_1_16 MAKE_ARCHIVE_VERSION(1, 16, 0)	/* add sequenceam */
 
 /* Current archive version number (the format we can output) */
 #define K_VERS_MAJOR 1
-#define K_VERS_MINOR 15
+#define K_VERS_MINOR 16
 #define K_VERS_REV 0
 #define K_VERS_SELF MAKE_ARCHIVE_VERSION(K_VERS_MAJOR, K_VERS_MINOR, K_VERS_REV)
 
@@ -319,6 +320,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 */
 
@@ -347,6 +349,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	   *owner;
 	char	   *desc;
@@ -387,6 +390,7 @@ typedef struct _archiveOpts
 	const char *tag;
 	const char *namespace;
 	const char *tablespace;
+	const char *sequenceam;
 	const char *tableam;
 	const char *owner;
 	const char *description;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 7347ca6ddb..07270b00e5 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -412,6 +412,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},
@@ -1016,6 +1017,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;
@@ -1131,6 +1133,7 @@ help(const char *progname)
 	printf(_("  --no-publications            do not dump publications\n"));
 	printf(_("  --no-security-labels         do not dump security label assignments\n"));
 	printf(_("  --no-subscriptions           do not dump subscriptions\n"));
+	printf(_("  --no-sequence-access-method  do not 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"));
@@ -13170,6 +13173,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;
@@ -17386,7 +17392,8 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 			   *maxv,
 			   *minv,
 			   *cache,
-			   *seqtype;
+			   *seqtype,
+			   *seqam;
 	bool		cycled;
 	bool		is_ascending;
 	int64		default_minv,
@@ -17400,13 +17407,35 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
-	if (fout->remoteVersion >= 100000)
+	if (fout->remoteVersion >= 170000)
 	{
+		/*
+		 * PostgreSQL 17 has added support for sequence access methods.
+		 */
+		appendPQExpBuffer(query,
+						  "SELECT format_type(s.seqtypid, NULL), "
+						  "s.seqstart, s.seqincrement, "
+						  "s.seqmax, s.seqmin, "
+						  "s.seqcache, s.seqcycle, "
+						  "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) "
+						  "WHERE s.seqrelid = '%u'::oid",
+						  tbinfo->dobj.catId.oid);
+	}
+	else if (fout->remoteVersion >= 100000)
+	{
+		/*
+		 * PostgreSQL 10 has moved sequence metadata to the catalog
+		 * pg_sequence.
+		 */
 		appendPQExpBuffer(query,
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
+						  "seqcache, seqcycle, "
+						  "'local' AS seqam "
 						  "FROM pg_catalog.pg_sequence "
 						  "WHERE seqrelid = '%u'::oid",
 						  tbinfo->dobj.catId.oid);
@@ -17422,7 +17451,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 		appendPQExpBuffer(query,
 						  "SELECT 'bigint' AS sequence_type, "
 						  "start_value, increment_by, max_value, min_value, "
-						  "cache_value, is_cycled FROM %s",
+						  "cache_value, is_cycled, 'local' as seqam FROM %s",
 						  fmtQualifiedDumpable(tbinfo));
 	}
 
@@ -17441,6 +17470,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	minv = PQgetvalue(res, 0, 4);
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
+	seqam = PQgetvalue(res, 0, 7);
 
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
@@ -17572,6 +17602,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 					 ARCHIVE_OPTS(.tag = tbinfo->dobj.name,
 								  .namespace = tbinfo->dobj.namespace->dobj.name,
 								  .owner = tbinfo->rolname,
+								  .sequenceam = 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 491311fe79..8a650f7fba 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -99,6 +99,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;
@@ -163,6 +164,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},
@@ -437,6 +439,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)
@@ -670,6 +674,7 @@ help(void)
 	printf(_("  --no-security-labels         do not dump security label assignments\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 c3beacdec1..0049130535 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -68,6 +68,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;
@@ -115,6 +116,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},
@@ -351,6 +353,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;
@@ -479,6 +482,7 @@ usage(const char *progname)
 	printf(_("  --no-publications            do not restore publications\n"));
 	printf(_("  --no-security-labels         do not restore security labels\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(_("  --section=SECTION            restore named section (pre-data, data, or post-data)\n"));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 3912dbf481..800d61391d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -548,6 +548,13 @@ 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', 'postgres',
+		],
+	},
 	no_table_access_method => {
 		dump_cmd => [
 			'pg_dump', '--no-sync',
@@ -722,6 +729,7 @@ my %full_runs = (
 	no_large_objects => 1,
 	no_owner => 1,
 	no_privs => 1,
+	no_sequence_access_method => 1,
 	no_table_access_method => 1,
 	pg_dumpall_dbprivs => 1,
 	pg_dumpall_exclude => 1,
@@ -3830,9 +3838,7 @@ my %tests = (
 		\QCREATE INDEX measurement_city_id_logdate_idx ON ONLY dump_test.measurement USING\E
 		/xm,
 		like => {
-			%full_runs,
-			%dump_test_schema_runs,
-			section_post_data => 1,
+			%full_runs, %dump_test_schema_runs, section_post_data => 1,
 		},
 		unlike => {
 			exclude_dump_test_schema => 1,
@@ -4501,6 +4507,18 @@ my %tests = (
 		},
 	},
 
+	'CREATE ACCESS METHOD regress_test_sequence_am' => {
+		create_order => 11,
+		create_sql =>
+		  'CREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER local_sequenceam_handler;',
+		regexp => qr/^
+			\QCREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER 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
@@ -4529,6 +4547,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 local;
+			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 local;',
+		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 => '
@@ -4723,10 +4770,8 @@ $node->command_fails_like(
 ##############################################################
 # Test dumping pg_catalog (for research -- cannot be reloaded)
 
-$node->command_ok(
-	[ 'pg_dump', '-p', "$port", '-n', 'pg_catalog' ],
-	'pg_dump: option -n pg_catalog'
-);
+$node->command_ok([ 'pg_dump', '-p', "$port", '-n', 'pg_catalog' ],
+	'pg_dump: option -n pg_catalog');
 
 #########################################
 # Test valid database exclusion patterns
@@ -4888,8 +4933,8 @@ foreach my $run (sort keys %pgdump_runs)
 		}
 		# Check for useless entries in "unlike" list.  Runs that are
 		# not listed in "like" don't need to be excluded in "unlike".
-		if ($tests{$test}->{unlike}->{$test_key} &&
-			!defined($tests{$test}->{like}->{$test_key}))
+		if ($tests{$test}->{unlike}->{$test_key}
+			&& !defined($tests{$test}->{like}->{$test_key}))
 		{
 			die "useless \"unlike\" entry \"$test_key\" in test \"$test\"";
 		}
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 0e5ba4f712..0dd0f5e0b4 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1083,6 +1083,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 4d7c046468..34643175fb 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -479,6 +479,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 1a23874da6..6581cff721 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -733,6 +733,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.43.0

v3-0010-dummy_sequence_am-Example-of-sequence-AM.patchtext/x-diff; charset=us-asciiDownload
From bef88fc57056ce07d585f964afe6e092dde03aa8 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:56:52 +0900
Subject: [PATCH v3 10/10] dummy_sequence_am: Example of sequence AM

---
 src/test/modules/Makefile                     |   1 +
 src/test/modules/dummy_sequence_am/.gitignore |   3 +
 src/test/modules/dummy_sequence_am/Makefile   |  19 +++
 .../dummy_sequence_am--1.0.sql                |  13 +++
 .../dummy_sequence_am/dummy_sequence_am.c     | 110 ++++++++++++++++++
 .../dummy_sequence_am.control                 |   5 +
 .../expected/dummy_sequence.out               |  35 ++++++
 .../modules/dummy_sequence_am/meson.build     |  33 ++++++
 .../dummy_sequence_am/sql/dummy_sequence.sql  |  17 +++
 src/test/modules/meson.build                  |   1 +
 10 files changed, 237 insertions(+)
 create mode 100644 src/test/modules/dummy_sequence_am/.gitignore
 create mode 100644 src/test/modules/dummy_sequence_am/Makefile
 create mode 100644 src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql
 create mode 100644 src/test/modules/dummy_sequence_am/dummy_sequence_am.c
 create mode 100644 src/test/modules/dummy_sequence_am/dummy_sequence_am.control
 create mode 100644 src/test/modules/dummy_sequence_am/expected/dummy_sequence.out
 create mode 100644 src/test/modules/dummy_sequence_am/meson.build
 create mode 100644 src/test/modules/dummy_sequence_am/sql/dummy_sequence.sql

diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index e32c8925f6..a0f5a36e60 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -10,6 +10,7 @@ SUBDIRS = \
 		  delay_execution \
 		  dummy_index_am \
 		  dummy_seclabel \
+		  dummy_sequence_am \
 		  libpq_pipeline \
 		  plsample \
 		  spgist_name_ops \
diff --git a/src/test/modules/dummy_sequence_am/.gitignore b/src/test/modules/dummy_sequence_am/.gitignore
new file mode 100644
index 0000000000..44d119cfcc
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/.gitignore
@@ -0,0 +1,3 @@
+# Generated subdirectories
+/log/
+/results/
diff --git a/src/test/modules/dummy_sequence_am/Makefile b/src/test/modules/dummy_sequence_am/Makefile
new file mode 100644
index 0000000000..391f7ac946
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/Makefile
@@ -0,0 +1,19 @@
+# src/test/modules/dummy_sequence_am/Makefile
+
+MODULES = dummy_sequence_am
+
+EXTENSION = dummy_sequence_am
+DATA = dummy_sequence_am--1.0.sql
+
+REGRESS = dummy_sequence
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/dummy_sequence_am
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql b/src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql
new file mode 100644
index 0000000000..e12b1f9d87
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql
@@ -0,0 +1,13 @@
+/* src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION dummy_sequence_am" to load this file. \quit
+
+CREATE FUNCTION dummy_sequenceam_handler(internal)
+  RETURNS sequence_am_handler
+  AS 'MODULE_PATHNAME'
+  LANGUAGE C;
+
+CREATE ACCESS METHOD dummy_sequence_am
+  TYPE SEQUENCE HANDLER dummy_sequenceam_handler;
+COMMENT ON ACCESS METHOD dummy_sequence_am IS 'dummy sequence access method';
diff --git a/src/test/modules/dummy_sequence_am/dummy_sequence_am.c b/src/test/modules/dummy_sequence_am/dummy_sequence_am.c
new file mode 100644
index 0000000000..b5ee5d89da
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/dummy_sequence_am.c
@@ -0,0 +1,110 @@
+/*-------------------------------------------------------------------------
+ *
+ * dummy_sequence_am.c
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/test/modules/dummy_sequence_am/dummy_sequence_am.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/sequenceam.h"
+#include "fmgr.h"
+
+PG_MODULE_MAGIC;
+
+/* this sequence is fully on-memory */
+static int	dummy_seqam_last_value = 1;
+static bool dummy_seqam_is_called = false;
+
+PG_FUNCTION_INFO_V1(dummy_sequenceam_handler);
+
+
+/* ------------------------------------------------------------------------
+ * Callbacks for the dummy 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 *
+dummy_sequenceam_get_table_am(void)
+{
+	return "heap";
+}
+
+static void
+dummy_sequenceam_init(Relation rel, int64 last_value, bool is_called)
+{
+	dummy_seqam_last_value = last_value;
+	dummy_seqam_is_called = is_called;
+}
+
+static int64
+dummy_sequenceam_nextval(Relation rel, int64 incby, int64 maxv,
+						 int64 minv, int64 cache, bool cycle,
+						 int64 *last)
+{
+	dummy_seqam_last_value += incby;
+	dummy_seqam_is_called = true;
+
+	return dummy_seqam_last_value;
+}
+
+static void
+dummy_sequenceam_setval(Relation rel, int64 next, bool iscalled)
+{
+	dummy_seqam_last_value = next;
+	dummy_seqam_is_called = iscalled;
+}
+
+static void
+dummy_sequenceam_get_state(Relation rel, int64 *last_value, bool *is_called)
+{
+	*last_value = dummy_seqam_last_value;
+	*is_called = dummy_seqam_is_called;
+}
+
+static void
+dummy_sequenceam_reset(Relation rel, int64 startv, bool is_called,
+					   bool reset_state)
+{
+	dummy_seqam_last_value = startv;
+	dummy_seqam_is_called = is_called;
+}
+
+static void
+dummy_sequenceam_change_persistence(Relation rel, char newrelpersistence)
+{
+	/* nothing to do, really */
+}
+
+/* ------------------------------------------------------------------------
+ * Definition of the dummy sequence access method.
+ * ------------------------------------------------------------------------
+ */
+
+static const SequenceAmRoutine dummy_sequenceam_methods = {
+	.type = T_SequenceAmRoutine,
+	.get_table_am = dummy_sequenceam_get_table_am,
+	.init = dummy_sequenceam_init,
+	.nextval = dummy_sequenceam_nextval,
+	.setval = dummy_sequenceam_setval,
+	.get_state = dummy_sequenceam_get_state,
+	.reset = dummy_sequenceam_reset,
+	.change_persistence = dummy_sequenceam_change_persistence
+};
+
+Datum
+dummy_sequenceam_handler(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_POINTER(&dummy_sequenceam_methods);
+}
diff --git a/src/test/modules/dummy_sequence_am/dummy_sequence_am.control b/src/test/modules/dummy_sequence_am/dummy_sequence_am.control
new file mode 100644
index 0000000000..9f10622f2f
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/dummy_sequence_am.control
@@ -0,0 +1,5 @@
+# dummy_sequence_am extension
+comment = 'dummy_sequence_am - sequence access method template'
+default_version = '1.0'
+module_pathname = '$libdir/dummy_sequence_am'
+relocatable = true
diff --git a/src/test/modules/dummy_sequence_am/expected/dummy_sequence.out b/src/test/modules/dummy_sequence_am/expected/dummy_sequence.out
new file mode 100644
index 0000000000..57588cea5b
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/expected/dummy_sequence.out
@@ -0,0 +1,35 @@
+CREATE EXTENSION dummy_sequence_am;
+CREATE SEQUENCE dummyseq USING dummy_sequence_am;
+SELECT nextval('dummyseq'::regclass);
+ nextval 
+---------
+       2
+(1 row)
+
+SELECT setval('dummyseq'::regclass, 14);
+ setval 
+--------
+     14
+(1 row)
+
+SELECT nextval('dummyseq'::regclass);
+ nextval 
+---------
+      15
+(1 row)
+
+-- Sequence relation exists, but it has no attributes.
+SELECT * FROM dummyseq;
+--
+(0 rows)
+
+-- Reset connection, which will reset the sequence
+\c
+SELECT nextval('dummyseq'::regclass);
+ nextval 
+---------
+       2
+(1 row)
+
+DROP SEQUENCE dummyseq;
+DROP EXTENSION dummy_sequence_am;
diff --git a/src/test/modules/dummy_sequence_am/meson.build b/src/test/modules/dummy_sequence_am/meson.build
new file mode 100644
index 0000000000..84460070e4
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+dummy_sequence_am_sources = files(
+  'dummy_sequence_am.c',
+)
+
+if host_system == 'windows'
+  dummy_sequence_am_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dummy_sequence_am',
+    '--FILEDESC', 'dummy_sequence_am - sequence access method template',])
+endif
+
+dummy_sequence_am = shared_module('dummy_sequence_am',
+  dummy_sequence_am_sources,
+  kwargs: pg_test_mod_args,
+)
+test_install_libs += dummy_sequence_am
+
+test_install_data += files(
+  'dummy_sequence_am.control',
+  'dummy_sequence_am--1.0.sql',
+)
+
+tests += {
+  'name': 'dummy_sequence_am',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'regress': {
+    'sql': [
+      'dummy_sequence',
+    ],
+  },
+}
diff --git a/src/test/modules/dummy_sequence_am/sql/dummy_sequence.sql b/src/test/modules/dummy_sequence_am/sql/dummy_sequence.sql
new file mode 100644
index 0000000000..c739b29a46
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/sql/dummy_sequence.sql
@@ -0,0 +1,17 @@
+CREATE EXTENSION dummy_sequence_am;
+
+CREATE SEQUENCE dummyseq USING dummy_sequence_am;
+
+SELECT nextval('dummyseq'::regclass);
+SELECT setval('dummyseq'::regclass, 14);
+SELECT nextval('dummyseq'::regclass);
+
+-- Sequence relation exists, but it has no attributes.
+SELECT * FROM dummyseq;
+
+-- Reset connection, which will reset the sequence
+\c
+SELECT nextval('dummyseq'::regclass);
+
+DROP SEQUENCE dummyseq;
+DROP EXTENSION dummy_sequence_am;
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 397e0906e6..d202ef087c 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -5,6 +5,7 @@ subdir('commit_ts')
 subdir('delay_execution')
 subdir('dummy_index_am')
 subdir('dummy_seclabel')
+subdir('dummy_sequence_am')
 subdir('injection_points')
 subdir('ldap_password_func')
 subdir('libpq_pipeline')
-- 
2.43.0

#8Peter Eisentraut
peter@eisentraut.org
In reply to: Matthias van de Meent (#4)
Re: Sequence Access Methods, round two

On 18.01.24 16:54, Matthias van de Meent wrote:

At $prevjob we had a use case for PRNG to generate small,
non-sequential "random" numbers without the birthday problem occurring
in sqrt(option space) because that'd increase the printed length of
the numbers beyond a set limit. The sequence API proposed here
would've been a great alternative to the solution we found, as it
would allow a sequence to be backed by an Linear Congruential
Generator directly, rather than the implementation of our own
transactional random_sequence table.

This is an interesting use case. I think what you'd need for that is
just the specification of a different "nextval" function and some
additional parameters (modulus, multiplier, and increment).

The proposed sequence AM patch would support a different nextval
function, but does it support additional parameters? I haven't found that.

Another use case I have wished for from time to time is creating
sequences using different data types, for example uuids. You'd just
need to provide a data-type-specific "next" function. However, in this
patch, all the values and state are hardcoded to int64.

While distributed systems can certainly use global int64 identifiers,
I'd expect that there would also be demand for uuids, so designing this
more flexibly would be useful.

I think the proposed patch covers too broad a range of abstraction
levels. The use cases described above are very high level and are just
concerned with how you get the next value. The current internal
sequence state would be stored in whatever way it is stored now. But
this patch also includes callbacks for very low-level-seeming concepts
like table AMs and persistence. Those seem like different things. And
the two levels should be combinable. Maybe I want a local sequence of
uuids or a global sequence of uuids, or a local sequence of integers or
a global sequence of integers. I mean, I haven't thought this through,
but I get the feeling that there should be more than one level of API
around this.

#9Michael Paquier
michael@paquier.xyz
In reply to: Peter Eisentraut (#8)
Re: Sequence Access Methods, round two

On Tue, Jan 23, 2024 at 10:58:50AM +0100, Peter Eisentraut wrote:

On 18.01.24 16:54, Matthias van de Meent wrote:
The proposed sequence AM patch would support a different nextval function,
but does it support additional parameters? I haven't found that.

Yes and no. Yes as in "the patch set can support it" and no as in
"the patch does not implement that yet". You could do two things
here:
- Add support for reloptions for sequences. The patch does not
include that on purpose because it already covers a lot of ground, and
that did not look like a strict requirement to me as a first shot. It
can be implemented on top of the patch set. That's not technically
complicated, actually, but there are some shenanigans to discuss with
the heap relation used under the hood by a sequence for the in-core
method or any other sequence AM that would need a sequence.
- Control that with GUCs defined in the AM, which may be weird, still
enough at relation level. And enough with the current patch set.

reloptions would make the most sense to me here, I assume, to ease we
handle use nextval().

Another use case I have wished for from time to time is creating sequences
using different data types, for example uuids. You'd just need to provide a
data-type-specific "next" function. However, in this patch, all the values
and state are hardcoded to int64.

Yeah, because all the cases I've seen would be happy with being able
to map a result to 8 bytes with a controlled computation method. The
size of the output generated, the set of data types that can be
supported by a table AM and the OID/name of the SQL function in charge
of retrieving the value could be controlled in the callbacks
themselves, and this would require a design of the callbacks. The
thing is that you *will* need callbacks and an AM layer to be able to
achieve that. I agree this can be useful. Now this is a separate
clause in the SEQUENCE DDLs, so it sounds to me like an entirely
different feature.

FWIW, MSSQL has a concept of custom data types for one, though these
need to map to integers (see user-defined_integer_type).

Another thing is the SQL specification. You or Vik will very likely
correct me here, but the spec mentions that sequences need to work on
integer values. A USING clause means that we already diverge from it,
perhaps it is OK to diverge more. How about DDL properties like
Min/Max or increment, then?

I think the proposed patch covers too broad a range of abstraction levels.
The use cases described above are very high level and are just concerned
with how you get the next value. The current internal sequence state would
be stored in whatever way it is stored now. But this patch also includes
callbacks for very low-level-seeming concepts like table AMs and
persistence. Those seem like different things. And the two levels should
be combinable. Maybe I want a local sequence of uuids or a global sequence
of uuids, or a local sequence of integers or a global sequence of integers.
I mean, I haven't thought this through, but I get the feeling that there
should be more than one level of API around this.

That's a tricky question, and I don't really know how far this needs
to go. FWIW, like table AMs I don't want the callbacks to be set in
stone across major releases. Now I am worrying a bit about designing
callbacks that are generic, still impact performance because they
require more catalog lookups and/or function point manipulations for
the default cases. Separating the computation and the in-core SQL
functions in a cleaner way is a step that helps in any case, IMO,
though I agree that the design of the callbacks influences how much is
exposed to users and AM developers. Having only a USING clause that
gives support to integer-based results while providing a way to force
the computation is useful. Custom data types that can be plugged into
the callbacks are also useful, still they are doing to require an AM
callback layer so as an AM can decide what it needs to do with the
data type given by the user in input of CREATE SEQUENCE.
--
Michael

#10Peter Eisentraut
peter@eisentraut.org
In reply to: Michael Paquier (#5)
Re: Sequence Access Methods, round two

On 19.01.24 00:27, Michael Paquier wrote:

The reason why this stuff has bumped into my desk is that we have no
good solution in-core for globally-distributed transactions for
active-active deployments. First, anything we have needs to be
plugged into default expressions of attributes like with [1] or [2],
or a tweak is to use sequence values that are computed with different
increments to avoid value overlaps across nodes. Both of these
require application changes, which is meh for a bunch of users.

I don't follow how these require "application changes". I guess it
depends on where you define the boundary of the "application". The
cited solutions require that you specify a different default expression
for "id" columns. Is that part of the application side? How would your
solution work on that level? AFAICT, you'd still need to specify the
sequence AM when you create the sequence or identity column. So you'd
need to modify the DDL code in any case.

#11Michael Paquier
michael@paquier.xyz
In reply to: Peter Eisentraut (#10)
Re: Sequence Access Methods, round two

On Thu, Feb 08, 2024 at 04:06:36PM +0100, Peter Eisentraut wrote:

On 19.01.24 00:27, Michael Paquier wrote:

The reason why this stuff has bumped into my desk is that we have no
good solution in-core for globally-distributed transactions for
active-active deployments. First, anything we have needs to be
plugged into default expressions of attributes like with [1] or [2],
or a tweak is to use sequence values that are computed with different
increments to avoid value overlaps across nodes. Both of these
require application changes, which is meh for a bunch of users.

I don't follow how these require "application changes". I guess it depends
on where you define the boundary of the "application".

Yep. There's a dependency to that.

The cited solutions
require that you specify a different default expression for "id" columns.
Is that part of the application side? How would your solution work on that
level? AFAICT, you'd still need to specify the sequence AM when you create
the sequence or identity column. So you'd need to modify the DDL code in
any case.

One idea is to rely on a GUC to control what is the default sequence
AM when taking the DefineRelation() path, so as the sequence AM
attached to a sequence is known for any DDL operation that may create
one internally, including generated columns. The patch set does that
with default_sequence_access_method, including support for
pg_dump[all] and pg_restore to give the possibility to one to force a
new default or just dump data without a specific AM (this uses SET
commands in-between the CREATE/ALTER commands).
--
Michael

#12Tomas Vondra
tomas.vondra@enterprisedb.com
In reply to: Michael Paquier (#7)
Re: Sequence Access Methods, round two

Hi Michael,

I took a quick look at this patch series, mostly to understand how it
works and how it might interact with the logical decoding patches
discussed in a nearby thread.

First, some general review comments:

0001
------

I think this bit in pg_proc.dat is not quite right:

proallargtypes => '{regclass,bool,int8}', proargmodes => '{i,o,o}',
proargnames => '{seqname,is_called,last_value}',

the first argument should not be "seqname" but rather "seqid".

0002, 0003
------------
seems fine, cosmetic changes

0004
------

I don't understand this bit in AlterSequence:

last_value = newdataform->last_value;
is_called = newdataform->is_called;

UnlockReleaseBuffer(buf);

/* Check and set new values */
init_params(pstate, stmt->options, stmt->for_identity, false,
seqform, &last_value, &reset_state, &is_called,
&need_seq_rewrite, &owned_by);

Why set the values only to pass them to init_params(), which will just
overwrite them anyway? Or do I get this wrong?

Also, isn't "reset_state" just a different name for (original) log_cnt?

0005
------

I don't quite understand what "elt" stands for :-(

stmt->tableElts = NIL;

Do we need AT_AddColumnToSequence? It seems to work exactly like
AT_AddColumn. OTOH we do have AT_AddColumnToView too ...

Thinking about this code:

case T_CreateSeqStmt:
EventTriggerAlterTableStart(parsetree);
address = DefineSequence(pstate, (CreateSeqStmt *) parsetree);
/* stashed internally */
commandCollected = true;
EventTriggerAlterTableEnd();
break;

Does this actually make sense? I mean, are sequences really relations?
Or was that just a side effect of storing the state in a heap table
(which is more of an implementation detail)?

0006
------
no comment, just moving code

0007
------
I wonder why heap_create_with_catalog needs to do this (check that it's
a sequence):

if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE) ||
relkind == RELKIND_SEQUENCE)

Presumably this is to handle sequences that use heap to store the state?
Maybe the comment should explain that. Also, will the other table AMs
need to do something similar, just in case some sequence happens to use
that table AM (which seems out of control of the table AM)?

I don't understand why DefineSequence need to copy the string:

stmt->accessMethod = seq->accessMethod ? pstrdup(seq->accessMethod)
: NULL;

RelationInitTableAccessMethod now does not need to handle sequences, or
rather should not be asked to handle sequences. Is there a risk we'd
pass a sequence to the function anyway? Maybe an assert / error would be
appropriate?

This bit in RelationBuildLocalRelation looks a bit weird ...

if (RELKIND_HAS_TABLE_AM(relkind))
RelationInitTableAccessMethod(rel);
else if (relkind == RELKIND_SEQUENCE)
RelationInitSequenceAccessMethod(rel);

It's not a fault of this patch, but shouldn't we now have something like
RELKIND_HAS_SEQUENCE_AM()?

0008-0010
-----------
no comment

logical decoding / replication
--------------------------------
Now, regarding the logical decoding / replication, would introducing the
sequence AM interfere with that in some way? Either in general, or with
respect to the nearby patch.

That is, what would it take to support logical replication of sequences
with some custom sequence AM? I believe that requires (a) synchronizing
the initial value, and (b) decoding the sequence WAL and (c) apply the
decoded changes. I don't think the sequence AM breaks any of this, as
long as it allows selecting "current value", decoding the values from
WAL, sending them to the subscriber, etc.

I guess the decoding would be up to the RMGR, and this patch maintains
the 1:1 mapping of sequences to relfilenodes, right? (That is, CREATE
and ALTER SEQUENCE would still create a new relfilenode, which is rather
important to decide if a sequence change is transactional.)

It seems to me this does not change the non-transactional behavior of
sequences, right?

alternative sequence AMs
--------------------------
I understand one of the reasons for adding sequence AMs is to allow
stuff like global/distributed sequences, etc. But will people actually
use that?

For example, I believe Simon originally proposed this in 2016 because
the plan was to implement distributed sequences in BDR on top of it. But
I believe BDR ultimately went with a very different approach, not with
custom sequence AMs. So I'm a bit skeptical this being suitable for
other active-active systems ...

Especially when the general consensus seems to be that for active-active
systems it's much better to use e.g. UUID, because that does not require
any coordination between the nodes, etc.

I'm not claiming there are no use cases for sequence AMs, of course. For
example the PRNG-based sequences mentioned by Mattias seems interesting.
I don't know how widely useful that is, though, and if it's worth it
(considering they managed to implement it in a different way).

But I think it might be a good idea to implement a PoC of such sequence
AM, if only to verify it can be implemented using the proposed code.

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#13Michael Paquier
michael@paquier.xyz
In reply to: Tomas Vondra (#12)
Re: Sequence Access Methods, round two

On Thu, Feb 22, 2024 at 05:36:00PM +0100, Tomas Vondra wrote:

0002, 0003
------------
seems fine, cosmetic changes

Thanks, I've applied these two for now. I'll reply to the rest
tomorrow or so.

By the way, I am really wondering if the update of elm->increment in
nextval_internal() should be treated as a bug? In the "fetch" cache
if a sequence does not use cycle, we may fail when reaching the upper
or lower bound for respectively an ascending or descending sequence,
while still keeping what could be an incorrect value if values are
cached on a follow-up nextval_internal call?
--
Michael

#14Matthias van de Meent
boekewurm+postgres@gmail.com
In reply to: Michael Paquier (#13)
Re: Sequence Access Methods, round two

On Mon, 26 Feb 2024 at 09:11, Michael Paquier <michael@paquier.xyz> wrote:

On Thu, Feb 22, 2024 at 05:36:00PM +0100, Tomas Vondra wrote:

0002, 0003
------------
seems fine, cosmetic changes

Thanks, I've applied these two for now. I'll reply to the rest
tomorrow or so.

Huh, that's surprising to me. I'd expected this to get at least a
final set of patches before they'd get committed. After a quick check
6e951bf seems fine, but I do have some nits on 449e798c:

+/* ----------------
+ *        validate_relation_kind - check the relation's kind
+ *
+ *        Make sure relkind is from an index

Shouldn't this be "... from a sequence"?

+ * ----------------
+ */
+static inline void
+validate_relation_kind(Relation r)

Shouldn't this be a bit more descriptive than just
"validate_relation_kind"? I notice this is no different from how this
is handled in index.c and table.c, but I'm not a huge fan of shadowing
names, even with static inlines functions.

-ERROR:  "serialtest1" is not a sequence
+ERROR:  cannot open relation "serialtest1"
+DETAIL:  This operation is not supported for tables.

We seem to lose some details here: We can most definitely open tables.
We just can't open them while treating them as sequences, which is not
mentioned in the error message.

Kind regards,

Matthias van de Meent
Neon (https://neon.tech)

#15Michael Paquier
michael@paquier.xyz
In reply to: Matthias van de Meent (#14)
Re: Sequence Access Methods, round two

On Mon, Feb 26, 2024 at 09:38:06AM +0100, Matthias van de Meent wrote:

On Mon, 26 Feb 2024 at 09:11, Michael Paquier <michael@paquier.xyz> wrote:

Thanks, I've applied these two for now. I'll reply to the rest
tomorrow or so.

Huh, that's surprising to me. I'd expected this to get at least a
final set of patches before they'd get committed.

FWIW, these refactoring pieces just make sense taken independently,
IMHO. I don't think that the rest of the patch set is going to make
it into v17, because there's no agreement about the layer we want,
which depend on the use cases we want to solve. Perhaps 0001 or 0004
could be salvaged. 0005~ had no real design discussion, so it's good
for 18~ as far as I am concerned. That's something that would be fit
for an unconference session at the next pgconf in Vancouver, in
combination with what we should do to support sequences across logical
replication setups.

After a quick check
6e951bf seems fine, but I do have some nits on 449e798c:

Thanks.

+/* ----------------
+ *        validate_relation_kind - check the relation's kind
+ *
+ *        Make sure relkind is from an index

Shouldn't this be "... from a sequence"?

Right, will fix.

+ * ----------------
+ */
+static inline void
+validate_relation_kind(Relation r)

Shouldn't this be a bit more descriptive than just
"validate_relation_kind"? I notice this is no different from how this
is handled in index.c and table.c, but I'm not a huge fan of shadowing
names, even with static inlines functions.

Not sure that it matters much, TBH. This is local to sequence.c.

-ERROR:  "serialtest1" is not a sequence
+ERROR:  cannot open relation "serialtest1"
+DETAIL:  This operation is not supported for tables.

We seem to lose some details here: We can most definitely open tables.
We just can't open them while treating them as sequences, which is not
mentioned in the error message.

I am not sure to agree with that. The user already knows that he
should be dealing with a sequence based on the DDL used, and we gain
information about the relkind getting manipulated here.
--
Michael

#16Michael Paquier
michael@paquier.xyz
In reply to: Tomas Vondra (#12)
8 attachment(s)
Re: Sequence Access Methods, round two

On Thu, Feb 22, 2024 at 05:36:00PM +0100, Tomas Vondra wrote:

I took a quick look at this patch series, mostly to understand how it
works and how it might interact with the logical decoding patches
discussed in a nearby thread.

Thanks. Both discussions are linked.

0001
------

I think this bit in pg_proc.dat is not quite right:

proallargtypes => '{regclass,bool,int8}', proargmodes => '{i,o,o}',
proargnames => '{seqname,is_called,last_value}',

the first argument should not be "seqname" but rather "seqid".

Ah, right. There are not many system functions that use regclass as
arguments, but the existing ones refer more to IDs, not names.

0002, 0003
------------
seems fine, cosmetic changes

Applied these ones as 449e798c77ed and 6e951bf98e2e.

0004
------

I don't understand this bit in AlterSequence:

last_value = newdataform->last_value;
is_called = newdataform->is_called;

UnlockReleaseBuffer(buf);

/* Check and set new values */
init_params(pstate, stmt->options, stmt->for_identity, false,
seqform, &last_value, &reset_state, &is_called,
&need_seq_rewrite, &owned_by);

Why set the values only to pass them to init_params(), which will just
overwrite them anyway? Or do I get this wrong?

The values of "last_value" and is_called may not get updated depending
on the options given in the ALTER SEQUENCE query, and they need to use
as initial state what's been returned from their last heap lookup.

Also, isn't "reset_state" just a different name for (original) log_cnt?

Yep. That's quite the point. That's an implementation detail
depending on the interface a sequence AM should use, but the main
argument behind this change is that log_cnt is a counter to decide
when to WAL-log the changes of a relation, but I have noticed that all
the paths of init_params() don't care about log_cnt as being a counter
at all: we just want to know if the state of a sequence should be
reset. Form_pg_sequence_data is a piece that only the in-core "local"
sequence AM cares about in this proposal.

0005
------

I don't quite understand what "elt" stands for :-(

stmt->tableElts = NIL;

Do we need AT_AddColumnToSequence? It seems to work exactly like
AT_AddColumn. OTOH we do have AT_AddColumnToView too ...

Yeah, that's just cleaner to use a separate one, to be able to detect
the attributes in the DDL deparsing pieces when gathering these pieces
with event triggers. At least that's my take once you extract the
piece that a sequence AM may need a table AM to store its data with
its own set of attributes (a sequence AM may as well not need a local
table for its data).

Thinking about this code:

case T_CreateSeqStmt:
EventTriggerAlterTableStart(parsetree);
address = DefineSequence(pstate, (CreateSeqStmt *) parsetree);
/* stashed internally */
commandCollected = true;
EventTriggerAlterTableEnd();
break;

Does this actually make sense? I mean, are sequences really relations?
Or was that just a side effect of storing the state in a heap table
(which is more of an implementation detail)?

This was becoming handy when creating custom attributes for the
underlying table used by a sequence.

Sequences are already relations (views are also relations), we store
them in pg_class. Now sequences can also use tables internally to
store their data, like the in-core "local" sequence AM defined in the
patch. At least that's the split done in this patch set.

0007
------
I wonder why heap_create_with_catalog needs to do this (check that it's
a sequence):

if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE) ||
relkind == RELKIND_SEQUENCE)

Presumably this is to handle sequences that use heap to store the state?
Maybe the comment should explain that. Also, will the other table AMs
need to do something similar, just in case some sequence happens to use
that table AM (which seems out of control of the table AM)?

Okay, I can see why this part can be confusing with the state of
things in v2. In DefineRelation(), heap_create_with_catalog() passes
down the OID of the sequence access method when creating a sequence,
not the OID of the table AM it may rely on. There's coverage for that
in the regression tests if you remove the check, see the "Try to drop
and fail on dependency" in create_am.sql.

You have a good point here: there could be a dependency between a
table AM and a sequence AM that may depend on it. The best way to
tackle that would be to add a DEPENDENCY_NORMAL on the amhandler of
the table AM when dealing with a sequence amtype in
CreateAccessMethod() in this design. Does that make sense?

(This may or may not make sense depending on how the design problem
related to the relationship between a sequence AM and its optional
table AM is tackled, of course, but at least it makes sense to me in
the scope of the design of this patch set.)

I don't understand why DefineSequence need to copy the string:

stmt->accessMethod = seq->accessMethod ? pstrdup(seq->accessMethod)
: NULL;

That's required to pass down the correct sequence AM for
DefineRelation() when creating the pg_class entry of a sequence.

RelationInitTableAccessMethod now does not need to handle sequences, or
rather should not be asked to handle sequences. Is there a risk we'd
pass a sequence to the function anyway? Maybe an assert / error would be
appropriate?

Hmm. The risk sounds legit. This is something where an assertion
based on RELKIND_HAS_TABLE_AM() would be useful. Same argument for
RelationInitSequenceAccessMethod() with RELKIND_HAS_SEQUENCE_AM()
suggested below. I've added these, for now.

This bit in RelationBuildLocalRelation looks a bit weird ...

if (RELKIND_HAS_TABLE_AM(relkind))
RelationInitTableAccessMethod(rel);
else if (relkind == RELKIND_SEQUENCE)
RelationInitSequenceAccessMethod(rel);

It's not a fault of this patch, but shouldn't we now have something like
RELKIND_HAS_SEQUENCE_AM()?

Perhaps, I was not sure. This would just be a check on
RELKIND_SEQUENCE, but perhaps that's worth having at the end, and this
makes the code more symmetric in the relcache, for one. The comment
at the top of RELKIND_HAS_TABLE_AM is wrong with 0007 in place anyway.

logical decoding / replication
--------------------------------
Now, regarding the logical decoding / replication, would introducing the
sequence AM interfere with that in some way? Either in general, or with
respect to the nearby patch.

I think it does not. The semantics of the existing in-core "local"
sequence AM are not changed. So what's here is just a large
refactoring shaped around the current semantics of the existing
computation method. Perhaps it should be smarter about some aspects,
but that's not something we'll know about until folks start
implementing their own custom methods. On my side, being able to plug
in a custom callback into nextval_internal() is the main taker.

That is, what would it take to support logical replication of sequences
with some custom sequence AM? I believe that requires (a) synchronizing
the initial value, and (b) decoding the sequence WAL and (c) apply the
decoded changes. I don't think the sequence AM breaks any of this, as
long as it allows selecting "current value", decoding the values from
WAL, sending them to the subscriber, etc.

Sure, that may make sense to support, particularly if one uses a
sequence AM that uses a computation method that may not be unique
across nodes, and where you may want to copy them. I don't think that
this is problem for something like the proposal of this thread or
what the other thread does, they can tackle separate areas (the
logirep patch has a lot of value for rolling upgrades where one uses
logical replication to create the new node and somebody does not want
to bother with a custom computation).

I guess the decoding would be up to the RMGR, and this patch maintains
the 1:1 mapping of sequences to relfilenodes, right? (That is, CREATE
and ALTER SEQUENCE would still create a new relfilenode, which is rather
important to decide if a sequence change is transactional.)

Yeah, one "local" sequence would have one relfilenode. A sequence AM
may want something different, like not using shared buffers, or just
not use a relfilenode at all.

It seems to me this does not change the non-transactional behavior of
sequences, right?

This patch set does nothing about the non-transactional behavior of
sequences. That seems out of scope to me from the start of what I
have sent here.

alternative sequence AMs
--------------------------
I understand one of the reasons for adding sequence AMs is to allow
stuff like global/distributed sequences, etc. But will people actually
use that?

Good question. I have users who would be happy with that, hiding
behind sequences custom computations rather than plug in a bunch of
default expressions to various attributes. You can do that today, but
this has this limitations depending on how much control one has over
their applications (for example this cannot be easily achieved with
generated columns in a schema).

For example, I believe Simon originally proposed this in 2016 because
the plan was to implement distributed sequences in BDR on top of it. But
I believe BDR ultimately went with a very different approach, not with
custom sequence AMs. So I'm a bit skeptical this being suitable for
other active-active systems ...

Snowflake IDs are popular AFAIK, thanks to the unicity of the values
across nodes.

Especially when the general consensus seems to be that for active-active
systems it's much better to use e.g. UUID, because that does not require
any coordination between the nodes, etc.

That means being able to support something larger than 64b values as
these are 128b.

I'm not claiming there are no use cases for sequence AMs, of course. For
example the PRNG-based sequences mentioned by Mattias seems interesting.
I don't know how widely useful that is, though, and if it's worth it
(considering they managed to implement it in a different way).

Right. I bet that they just plugged a default expression to the
attributes involved. When it comes to users at a large scale, a
sequence AM makes the change more transparent, especially if DDL
queries are replication across multiple logical nodes.

But I think it might be a good idea to implement a PoC of such sequence
AM, if only to verify it can be implemented using the proposed code.

You mean the PRNG idea or something else? I have a half-baked
implementation for snowflake, actually. Would that be enough? I
still need to spend more hours on it to polish it. One part I found
more difficult than necessary with the patch set of this thread is the
APIs used in commands/sequence.c for the buffer manipulations,
requiring more duplications. Not impossible to do outside core, but
I've wanted more refactoring of the routines used by the "local"
sequence AM of this patch.

Plugging in a custom data type on top of the existing sequence objects
is something entirely different, where we will need a callback
separation anyway at the end, IMHO. This seems like a separate topic
to me at the end, as custom computations with 64b to store them is
enough based on what I've heard even for hundreds of nodes. I may be
wrong and may not think big enough, of course.

Attaching a v3 set, fixing one conflict, while on it.
--
Michael

Attachments:

v3-0001-Switch-pg_sequence_last_value-to-report-a-tuple-a.patchtext/x-diff; charset=us-asciiDownload
From dd5ac740db59de73723fffb4c1ad22dc1ba64816 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 27 Feb 2024 09:02:57 +0900
Subject: [PATCH v3 1/8] Switch pg_sequence_last_value() to report a tuple and
 use it in pg_dump

This commit switches pg_sequence_last_value() to report a tuple made of
(last_value,is_called) that can be directly be used for the arguments of
setval() in a sequence.

Going forward with PostgreSQL 17, pg_dump and pg_sequences make use of
it instead of scanning the heap table assumed to always exist for a
sequence.

Note: this requires a catversion bump.
---
 src/include/catalog/pg_proc.dat      |  6 ++++--
 src/backend/catalog/system_views.sql |  6 +++++-
 src/backend/commands/sequence.c      | 19 +++++++++++++------
 src/bin/pg_dump/pg_dump.c            | 16 +++++++++++++---
 src/test/regress/expected/rules.out  |  7 ++++++-
 5 files changed, 41 insertions(+), 13 deletions(-)

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9c120fc2b7..1b09705f2a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3325,9 +3325,11 @@
   proargmodes => '{i,o,o,o,o,o,o,o}',
   proargnames => '{sequence_oid,start_value,minimum_value,maximum_value,increment,cycle_option,cache_size,data_type}',
   prosrc => 'pg_sequence_parameters' },
-{ oid => '4032', descr => 'sequence last value',
+{ oid => '4032', descr => 'sequence last value data',
   proname => 'pg_sequence_last_value', provolatile => 'v', proparallel => 'u',
-  prorettype => 'int8', proargtypes => 'regclass',
+  prorettype => 'record', proargtypes => 'regclass',
+  proallargtypes => '{regclass,bool,int8}', proargmodes => '{i,o,o}',
+  proargnames => '{seqid,is_called,last_value}',
   prosrc => 'pg_sequence_last_value' },
 
 { oid => '275', descr => 'return the next oid for a system table',
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 04227a72d1..30d0700d31 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -178,7 +178,11 @@ CREATE VIEW pg_sequences AS
         S.seqcache AS cache_size,
         CASE
             WHEN has_sequence_privilege(C.oid, 'SELECT,USAGE'::text)
-                THEN pg_sequence_last_value(C.oid)
+                THEN (SELECT
+                          CASE WHEN sl.is_called
+                              THEN sl.last_value ELSE NULL
+                          END
+                      FROM pg_sequence_last_value(C.oid) sl)
             ELSE NULL
         END AS last_value
     FROM pg_sequence S JOIN pg_class C ON (C.oid = S.seqrelid)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 1bed9c74d1..f122e943fb 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1774,14 +1774,22 @@ pg_sequence_parameters(PG_FUNCTION_ARGS)
 Datum
 pg_sequence_last_value(PG_FUNCTION_ARGS)
 {
+#define PG_SEQUENCE_LAST_VALUE_COLS		2
 	Oid			relid = PG_GETARG_OID(0);
+	Datum		values[PG_SEQUENCE_LAST_VALUE_COLS] = {0};
+	bool		nulls[PG_SEQUENCE_LAST_VALUE_COLS] = {0};
 	SeqTable	elm;
 	Relation	seqrel;
+	TupleDesc	tupdesc;
 	Buffer		buf;
 	HeapTupleData seqtuple;
 	Form_pg_sequence_data seq;
 	bool		is_called;
-	int64		result;
+	int64		last_value;
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	/* open and lock sequence */
 	init_sequence(relid, &elm, &seqrel);
@@ -1795,15 +1803,14 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	seq = read_seq_tuple(seqrel, &buf, &seqtuple);
 
 	is_called = seq->is_called;
-	result = seq->last_value;
+	last_value = seq->last_value;
 
 	UnlockReleaseBuffer(buf);
 	sequence_close(seqrel, NoLock);
 
-	if (is_called)
-		PG_RETURN_INT64(result);
-	else
-		PG_RETURN_NULL();
+	values[0] = BoolGetDatum(is_called);
+	values[1] = Int64GetDatum(last_value);
+	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
 }
 
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 2225a12718..4d50a3e336 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -17682,9 +17682,19 @@ dumpSequenceData(Archive *fout, const TableDataInfo *tdinfo)
 	bool		called;
 	PQExpBuffer query = createPQExpBuffer();
 
-	appendPQExpBuffer(query,
-					  "SELECT last_value, is_called FROM %s",
-					  fmtQualifiedDumpable(tbinfo));
+	/*
+	 * In versions 17 and up, pg_sequence_last_value() has been switched to
+	 * return a tuple with last_value and is_called.
+	 */
+	if (fout->remoteVersion >= 170000)
+		appendPQExpBuffer(query,
+						  "SELECT last_value, is_called "
+						  "FROM pg_sequence_last_value('%s')",
+						  fmtQualifiedDumpable(tbinfo));
+	else
+		appendPQExpBuffer(query,
+						  "SELECT last_value, is_called FROM %s",
+						  fmtQualifiedDumpable(tbinfo));
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index b7488d760e..b490348675 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1698,7 +1698,12 @@ pg_sequences| SELECT n.nspname AS schemaname,
     s.seqcycle AS cycle,
     s.seqcache AS cache_size,
         CASE
-            WHEN has_sequence_privilege(c.oid, 'SELECT,USAGE'::text) THEN pg_sequence_last_value((c.oid)::regclass)
+            WHEN has_sequence_privilege(c.oid, 'SELECT,USAGE'::text) THEN ( SELECT
+                    CASE
+                        WHEN sl.is_called THEN sl.last_value
+                        ELSE NULL::bigint
+                    END AS "case"
+               FROM pg_sequence_last_value((c.oid)::regclass) sl(is_called, last_value))
             ELSE NULL::bigint
         END AS last_value
    FROM ((pg_sequence s
-- 
2.43.0

v3-0002-Remove-FormData_pg_sequence_data-from-init_params.patchtext/x-diff; charset=us-asciiDownload
From 3a71c864a0a8a26cf8d88ad940ccee4554df5f7b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:00:45 +0900
Subject: [PATCH v3 2/8] Remove FormData_pg_sequence_data from
 init_params()/sequence.c

init_params() sets up "last_value" and "is_called" for a sequence, based
on the sequence properties in pg_sequences.  This simplifies the logic
around log_cnt, which is reset to 0 when the metadata of a sequence is
expected to start from afresh when its properties are updated.
---
 src/backend/commands/sequence.c | 81 ++++++++++++++++++++-------------
 1 file changed, 49 insertions(+), 32 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index f122e943fb..758d32dd45 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -106,7 +106,9 @@ static Form_pg_sequence_data read_seq_tuple(Relation rel,
 static void init_params(ParseState *pstate, List *options, bool for_identity,
 						bool isInit,
 						Form_pg_sequence seqform,
-						Form_pg_sequence_data seqdataform,
+						int64 *last_value,
+						bool *reset_state,
+						bool *is_called,
 						bool *need_seq_rewrite,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
@@ -121,7 +123,9 @@ ObjectAddress
 DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 {
 	FormData_pg_sequence seqform;
-	FormData_pg_sequence_data seqdataform;
+	int64		last_value;
+	bool		reset_state;
+	bool		is_called;
 	bool		need_seq_rewrite;
 	List	   *owned_by;
 	CreateStmt *stmt = makeNode(CreateStmt);
@@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* Check and set all option values */
 	init_params(pstate, seq->options, seq->for_identity, true,
-				&seqform, &seqdataform,
+				&seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/*
@@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 		{
 			case SEQ_COL_LASTVAL:
 				coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
-				value[i - 1] = Int64GetDatumFast(seqdataform.last_value);
+				value[i - 1] = Int64GetDatumFast(last_value);
 				break;
 			case SEQ_COL_LOG:
 				coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
@@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	ObjectAddress address;
 	Relation	rel;
 	HeapTuple	seqtuple;
+	bool		reset_state = false;
+	bool		is_called;
+	int64		last_value;
 	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
@@ -481,12 +488,14 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	/* 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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
-				seqform, newdataform,
+				seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/* If needed, rewrite the sequence relation itself */
@@ -513,6 +522,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * 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);
 	}
 
@@ -1229,17 +1242,19 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 /*
  * init_params: process the options list of CREATE or ALTER SEQUENCE, and
  * store the values into appropriate fields of seqform, for changes that go
- * into the pg_sequence catalog, and fields of seqdataform for changes to the
- * sequence relation itself.  Set *need_seq_rewrite to true if we changed any
- * parameters that require rewriting the sequence's relation (interesting for
- * ALTER SEQUENCE).  Also set *owned_by to any OWNED BY option, or to NIL if
- * there is none.
+ * into the pg_sequence catalog, and fields for changes to the sequence
+ * relation itself (is_called, last_value or any state it may hold).  Set
+ * *need_seq_rewrite to true if we changed any parameters that require
+ * rewriting the sequence's relation (interesting for ALTER SEQUENCE).  Also
+ * set *owned_by to any OWNED BY option, or to NIL if there is none.  Set
+ * *reset_state if the internal state of the sequence needs to change on a
+ * follow-up nextval().
  *
  * If isInit is true, fill any unspecified options with default values;
  * otherwise, do not change existing options that aren't explicitly overridden.
  *
  * Note: we force a sequence rewrite whenever we change parameters that affect
- * generation of future sequence values, even if the seqdataform per se is not
+ * generation of future sequence values, even if the metadata per se is not
  * changed.  This allows ALTER SEQUENCE to behave transactionally.  Currently,
  * the only option that doesn't cause that is OWNED BY.  It's *necessary* for
  * ALTER SEQUENCE OWNED BY to not rewrite the sequence, because that would
@@ -1250,7 +1265,9 @@ static void
 init_params(ParseState *pstate, List *options, bool for_identity,
 			bool isInit,
 			Form_pg_sequence seqform,
-			Form_pg_sequence_data seqdataform,
+			int64 *last_value,
+			bool *reset_state,
+			bool *is_called,
 			bool *need_seq_rewrite,
 			List **owned_by)
 {
@@ -1353,11 +1370,11 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	}
 
 	/*
-	 * We must reset log_cnt when isInit or when changing any parameters that
-	 * would affect future nextval allocations.
+	 * We must reset the state when isInit or when changing any parameters
+	 * that would affect future nextval allocations.
 	 */
 	if (isInit)
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 
 	/* AS type */
 	if (as_type != NULL)
@@ -1406,7 +1423,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("INCREMENT must not be zero")));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1418,7 +1435,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	{
 		seqform->seqcycle = boolVal(is_cycled->arg);
 		Assert(BoolIsValid(seqform->seqcycle));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1429,7 +1446,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (max_value != NULL && max_value->arg)
 	{
 		seqform->seqmax = defGetInt64(max_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || max_value != NULL || reset_max_value)
 	{
@@ -1445,7 +1462,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmax = -1;	/* descending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate maximum value.  No need to check INT8 as seqmax is an int64 */
@@ -1461,7 +1478,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (min_value != NULL && min_value->arg)
 	{
 		seqform->seqmin = defGetInt64(min_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || min_value != NULL || reset_min_value)
 	{
@@ -1477,7 +1494,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmin = 1;	/* ascending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate minimum value.  No need to check INT8 as seqmin is an int64 */
@@ -1528,30 +1545,30 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (restart_value != NULL)
 	{
 		if (restart_value->arg != NULL)
-			seqdataform->last_value = defGetInt64(restart_value);
+			*last_value = defGetInt64(restart_value);
 		else
-			seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
-		seqdataform->log_cnt = 0;
+			*last_value = seqform->seqstart;
+		*is_called = false;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-		seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
+		*last_value = seqform->seqstart;
+		*is_called = false;
 	}
 
 	/* crosscheck RESTART (or current value, if changing MIN/MAX) */
-	if (seqdataform->last_value < seqform->seqmin)
+	if (*last_value < seqform->seqmin)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be less than MINVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmin)));
-	if (seqdataform->last_value > seqform->seqmax)
+	if (*last_value > seqform->seqmax)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be greater than MAXVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmax)));
 
 	/* CACHE */
@@ -1563,7 +1580,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("CACHE (%lld) must be greater than zero",
 							(long long) seqform->seqcache)));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-- 
2.43.0

v3-0003-Integrate-addition-of-attributes-for-sequences-wi.patchtext/x-diff; charset=us-asciiDownload
From bdab72d4dd20d160aaae0f04690849a4c697f7ab Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v3 3/8] 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               | 29 +++++++++++++++++--
 src/backend/commands/tablecmds.c              | 10 +++++++
 src/backend/tcop/utility.c                    |  4 +++
 .../test_ddl_deparse/expected/alter_table.out | 10 +++++--
 .../expected/create_sequence_1.out            |  5 +++-
 .../expected/create_table.out                 | 15 ++++++++--
 .../test_ddl_deparse/test_ddl_deparse.c       |  3 ++
 8 files changed, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index baa6a97c7e..7ce145783d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2186,6 +2186,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 758d32dd45..2d9faf5dd6 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -136,6 +136,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;
@@ -174,7 +177,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;
@@ -198,7 +200,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;
@@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 f798794556..ee9dffdf15 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4544,6 +4544,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 */
@@ -4839,6 +4840,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,
@@ -5262,6 +5270,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,
@@ -6417,6 +6426,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 8de821f960..fd60678add 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1671,7 +1671,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 b5e71af9aa..7ebd85200f 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
@@ -71,7 +74,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_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
index 5837ea484e..310ce5a6ba 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.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 75b62aff4d..69e54358ee 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 alter table, tag ALTER TABLE
 NOTICE:    subcommand: type SET ATTNOTNULL desc <NULL>
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 48563b2cf0..bba80a7a3d 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -114,6 +114,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.43.0

v3-0004-Move-code-for-local-sequences-to-own-file.patchtext/x-diff; charset=us-asciiDownload
From fc4cbe04a5b48b0fbd0d9b3effc3435a22ab3b69 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:01:55 +0900
Subject: [PATCH v3 4/8] Move code for local sequences to own file

Now that the separation between the in-core sequence computations and
the catalog layer is clean, this moves the code corresponding to the
"local" sequence AM into its own file, out of sequence.c.  The WAL
routines related to sequence are moved in it as well.
---
 src/include/access/localam.h                  |  48 ++
 src/include/access/rmgrlist.h                 |   2 +-
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 .../rmgrdesc/{seqdesc.c => localseqdesc.c}    |  18 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/local.c           | 706 ++++++++++++++++++
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 619 +--------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 12 files changed, 793 insertions(+), 611 deletions(-)
 create mode 100644 src/include/access/localam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => localseqdesc.c} (69%)
 create mode 100644 src/backend/access/sequence/local.c

diff --git a/src/include/access/localam.h b/src/include/access/localam.h
new file mode 100644
index 0000000000..5b0575dc2e
--- /dev/null
+++ b/src/include/access/localam.h
@@ -0,0 +1,48 @@
+/*-------------------------------------------------------------------------
+ *
+ * localam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/localam.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef LOCALAM_H
+#define LOCALAM_H
+
+#include "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_LOCAL_SEQ_LOG			0x00
+
+typedef struct xl_local_seq_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_local_seq_rec;
+
+extern void local_seq_redo(XLogReaderState *record);
+extern void local_seq_desc(StringInfo buf, XLogReaderState *record);
+extern const char *local_seq_identify(uint8 info);
+extern void local_seq_mask(char *page, BlockNumber blkno);
+
+/* access routines */
+extern int64 local_seq_nextval(Relation rel, int64 incby, int64 maxv,
+							   int64 minv, int64 cache, bool cycle,
+							   int64 *last);
+extern const char *local_seq_get_table_am(void);
+extern void local_seq_init(Relation rel, int64 last_value, bool is_called);
+extern void local_seq_setval(Relation rel, int64 next, bool iscalled);
+extern void local_seq_reset(Relation rel, int64 startv, bool is_called,
+							bool reset_state);
+extern void local_seq_get_state(Relation rel, int64 *last_value,
+								bool *is_called);
+extern void local_seq_change_persistence(Relation rel,
+										 char newrelpersistence);
+
+#endif							/* LOCALAM_H */
diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 78e6b908c6..46fd63ae47 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_LOCAL_SEQ_ID, "LocalSequence", local_seq_redo, local_seq_desc, local_seq_identify, NULL, NULL, local_seq_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/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile
index cd95eec37f..dff5a60e68 100644
--- a/src/backend/access/rmgrdesc/Makefile
+++ b/src/backend/access/rmgrdesc/Makefile
@@ -18,13 +18,13 @@ OBJS = \
 	gistdesc.o \
 	hashdesc.o \
 	heapdesc.o \
+	localseqdesc.o \
 	logicalmsgdesc.o \
 	mxactdesc.o \
 	nbtdesc.o \
 	relmapdesc.o \
 	replorigindesc.o \
 	rmgrdesc_utils.o \
-	seqdesc.o \
 	smgrdesc.o \
 	spgdesc.o \
 	standbydesc.o \
diff --git a/src/backend/access/rmgrdesc/seqdesc.c b/src/backend/access/rmgrdesc/localseqdesc.c
similarity index 69%
rename from src/backend/access/rmgrdesc/seqdesc.c
rename to src/backend/access/rmgrdesc/localseqdesc.c
index cf0e02ded5..17b8b71093 100644
--- a/src/backend/access/rmgrdesc/seqdesc.c
+++ b/src/backend/access/rmgrdesc/localseqdesc.c
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
- * seqdesc.c
- *	  rmgr descriptor routines for commands/sequence.c
+ * localseqdesc.c
+ *	  rmgr descriptor routines for sequence/local.c
  *
  * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -14,31 +14,31 @@
  */
 #include "postgres.h"
 
-#include "commands/sequence.h"
+#include "access/localam.h"
 
 
 void
-seq_desc(StringInfo buf, XLogReaderState *record)
+local_seq_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_local_seq_rec *xlrec = (xl_local_seq_rec *) rec;
 
-	if (info == XLOG_SEQ_LOG)
+	if (info == XLOG_LOCAL_SEQ_LOG)
 		appendStringInfo(buf, "rel %u/%u/%u",
 						 xlrec->locator.spcOid, xlrec->locator.dbOid,
 						 xlrec->locator.relNumber);
 }
 
 const char *
-seq_identify(uint8 info)
+local_seq_identify(uint8 info)
 {
 	const char *id = NULL;
 
 	switch (info & ~XLR_INFO_MASK)
 	{
-		case XLOG_SEQ_LOG:
-			id = "LOG";
+		case XLOG_LOCAL_SEQ_LOG:
+			id = "LOCAL_SEQ_LOG";
 			break;
 	}
 
diff --git a/src/backend/access/rmgrdesc/meson.build b/src/backend/access/rmgrdesc/meson.build
index e8b7a65fc7..5f2f28072b 100644
--- a/src/backend/access/rmgrdesc/meson.build
+++ b/src/backend/access/rmgrdesc/meson.build
@@ -11,13 +11,13 @@ rmgr_desc_sources = files(
   'gistdesc.c',
   'hashdesc.c',
   'heapdesc.c',
+  'localseqdesc.c',
   'logicalmsgdesc.c',
   'mxactdesc.c',
   'nbtdesc.c',
   'relmapdesc.c',
   'replorigindesc.c',
   'rmgrdesc_utils.c',
-  'seqdesc.c',
   'smgrdesc.c',
   'spgdesc.c',
   'standbydesc.c',
diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile
index 9f9d31f542..697f89905e 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = local.o sequence.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/sequence/local.c b/src/backend/access/sequence/local.c
new file mode 100644
index 0000000000..e77f25e13e
--- /dev/null
+++ b/src/backend/access/sequence/local.c
@@ -0,0 +1,706 @@
+/*-------------------------------------------------------------------------
+ *
+ * local.c
+ *	  Local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/access/sequence/local.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/localam.h"
+#include "access/multixact.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"
+
+
+/*
+ * 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 LOCAL_SEQ_LOG_VALS	32
+
+/*
+ * The "special area" of a local sequence's buffer page looks like this.
+ */
+#define LOCAL_SEQ_MAGIC	  0x1717
+
+typedef struct local_sequence_magic
+{
+	uint32		magic;
+} local_sequence_magic;
+
+/* Format of tuples stored in heap table associated to local sequences */
+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 local 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
+
+
+/*
+ * 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
+
+static Form_pg_sequence_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_sequence_data
+read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
+{
+	Page		page;
+	ItemId		lp;
+	local_sequence_magic *sm;
+	Form_pg_sequence_data seq;
+
+	*buf = ReadBuffer(rel, 0);
+	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(*buf);
+	sm = (local_sequence_magic *) PageGetSpecialPointer(page);
+
+	if (sm->magic != LOCAL_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;
+}
+
+/*
+ * 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, InvalidBackendId);
+		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;
+	local_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(local_sequence_magic));
+	sm = (local_sequence_magic *) PageGetSpecialPointer(page);
+	sm->magic = LOCAL_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, (Item) 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_local_seq_rec xlrec;
+		XLogRecPtr	recptr;
+
+		XLogBeginInsert();
+		XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
+
+		xlrec.locator = rel->rd_locator;
+
+		XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec));
+		XLogRegisterData((char *) tuple->t_data, tuple->t_len);
+
+		recptr = XLogInsert(RM_LOCAL_SEQ_ID, XLOG_LOCAL_SEQ_LOG);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buf);
+}
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+local_seq_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+local_seq_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_local_seq_rec *xlrec = (xl_local_seq_rec *) XLogRecGetData(record);
+	local_sequence_magic *sm;
+
+	if (info != XLOG_LOCAL_SEQ_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = (Page) BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	PageInit(localpage, BufferGetPageSize(buffer), sizeof(local_sequence_magic));
+	sm = (local_sequence_magic *) PageGetSpecialPointer(localpage);
+	sm->magic = LOCAL_SEQ_MAGIC;
+
+	item = (char *) xlrec + sizeof(xl_local_seq_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_local_seq_rec);
+
+	if (PageAddItem(localpage, (Item) item, itemsz,
+					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "local_seq_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
+
+/*
+ * local_seq_nextval()
+ *
+ * Allocate a new value for a local sequence, based on the sequence
+ * configuration.
+ */
+int64
+local_seq_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_sequence_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_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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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_local_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 = rel->rd_locator;
+
+		XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec));
+		XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len);
+
+		recptr = XLogInsert(RM_LOCAL_SEQ_ID, XLOG_LOCAL_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);
+
+	return result;
+}
+
+/*
+ * local_seq_get_table_am()
+ *
+ * Return the table access method used by this sequence.
+ */
+const char *
+local_seq_get_table_am(void)
+{
+	return "heap";
+}
+
+/*
+ * local_seq_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
+local_seq_init(Relation rel, int64 last_value, bool is_called)
+{
+	Datum		value[SEQ_COL_LASTCOL];
+	bool		null[SEQ_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_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(0);
+				break;
+			case SEQ_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);
+}
+
+/*
+ * local_seq_setval()
+ *
+ * Callback for setval().
+ */
+void
+local_seq_setval(Relation rel, int64 next, bool iscalled)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_sequence_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_local_seq_rec xlrec;
+		XLogRecPtr	recptr;
+		Page		page = BufferGetPage(buf);
+
+		XLogBeginInsert();
+		XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
+
+		xlrec.locator = rel->rd_locator;
+		XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec));
+		XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len);
+
+		recptr = XLogInsert(RM_LOCAL_SEQ_ID, XLOG_LOCAL_SEQ_LOG);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buf);
+}
+
+/*
+ * local_seq_reset()
+ *
+ * Perform a hard reset on the local sequence, rewriting its heap data
+ * entirely.
+ */
+void
+local_seq_reset(Relation rel, int64 startv, bool is_called, bool reset_state)
+{
+	Form_pg_sequence_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_sequence_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);
+}
+
+/*
+ * local_seq_get_state()
+ *
+ * Retrieve the state of a local sequence.
+ */
+void
+local_seq_get_state(Relation rel, int64 *last_value, bool *is_called)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_sequence_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	*last_value = seq->last_value;
+	*is_called = seq->is_called;
+
+	UnlockReleaseBuffer(buf);
+}
+
+/*
+ * local_seq_change_persistence()
+ *
+ * Persistence change for the local sequence Relation.
+ */
+void
+local_seq_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/sequence/meson.build b/src/backend/access/sequence/meson.build
index 1843aed38d..8c4ef42467 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,6 @@
 # Copyright (c) 2022-2024, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'local.c',
   'sequence.c',
 )
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 7d67eda5f7..c3f9acb064 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -15,6 +15,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/localam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 2d9faf5dd6..f69ee063b2 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/localam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -50,23 +51,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -96,13 +80,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,
@@ -134,14 +114,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
@@ -174,35 +148,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;
@@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	/* now initialize the sequence table structure and its data */
+	local_seq_init(rel, last_value, is_called);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -292,10 +222,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;
@@ -306,7 +232,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))
@@ -315,40 +240,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. */
+	local_seq_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 */
@@ -357,106 +250,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, InvalidBackendId);
-		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, (Item) 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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) tuple->t_data, tuple->t_len);
-
-		recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	UnlockReleaseBuffer(buf);
-}
-
 /*
  * AlterSequence
  *
@@ -468,10 +261,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;
@@ -480,7 +270,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	bool		reset_state = false;
 	bool		is_called;
 	int64		last_value;
-	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
 	relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -507,16 +296,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 */
+	local_seq_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -526,32 +307,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);
+		local_seq_reset(seqrel, last_value, is_called, reset_state);
 	}
 
 	/* Clear local cache so that we don't think we have cached numbers */
@@ -580,8 +339,6 @@ SequenceChangePersistence(Oid relid, char newrelpersistence)
 {
 	SeqTable	elm;
 	Relation	seqrel;
-	Buffer		buf;
-	HeapTupleData seqdatatuple;
 
 	init_sequence(relid, &elm, &seqrel);
 
@@ -589,10 +346,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);
+	local_seq_change_persistence(seqrel, newrelpersistence);
 
 	sequence_close(seqrel, NoLock);
 }
@@ -655,24 +409,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);
@@ -717,105 +462,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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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 = local_seq_nextval(seqrel, incby, maxv, minv, cache, cycle,
+							   &last);
 
 	/* save info in local cache */
 	elm->increment = incby;
@@ -825,69 +474,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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) 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;
@@ -977,9 +563,6 @@ do_setval(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,
@@ -1013,9 +596,6 @@ do_setval(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),
@@ -1037,37 +617,8 @@ do_setval(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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) 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 */
+	local_seq_setval(seqrel, next, iscalled);
 
 	sequence_close(seqrel, NoLock);
 }
@@ -1208,62 +759,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
@@ -1823,9 +1318,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	SeqTable	elm;
 	Relation	seqrel;
 	TupleDesc	tupdesc;
-	Buffer		buf;
-	HeapTupleData seqtuple;
-	Form_pg_sequence_data seq;
 	bool		is_called;
 	int64		last_value;
 
@@ -1842,12 +1334,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 				 errmsg("permission denied for sequence %s",
 						RelationGetRelationName(seqrel))));
 
-	seq = read_seq_tuple(seqrel, &buf, &seqtuple);
-
-	is_called = seq->is_called;
-	last_value = seq->last_value;
-
-	UnlockReleaseBuffer(buf);
+	local_seq_get_state(seqrel, &last_value, &is_called);
 	sequence_close(seqrel, NoLock);
 
 	values[0] = BoolGetDatum(is_called);
@@ -1855,57 +1342,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
 }
 
-
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = (Page) BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, (Item) item, itemsz,
-					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1920,14 +1356,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c76..0f45509f2c 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/localseqdesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index 6b8c17bb4c..ff09335607 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/localam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
-- 
2.43.0

v3-0005-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From 328dacaf3e5f5b76ca915b076bbfb7c05d135cd4 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 27 Feb 2024 10:18:26 +0900
Subject: [PATCH v3 5/8] Sequence access methods - backend support

The "local" sequence AM is now plugged in as a handler in the relcache,
and a set of callbacks in sequenceam.h.
---
 src/include/access/localam.h                  |  15 --
 src/include/access/sequenceam.h               | 188 ++++++++++++++++++
 src/include/catalog/pg_am.dat                 |   3 +
 src/include/catalog/pg_am.h                   |   1 +
 src/include/catalog/pg_class.h                |  10 +-
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/local.c           |  69 ++++---
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/sequence.c        |   3 +-
 src/backend/access/sequence/sequenceamapi.c   | 145 ++++++++++++++
 src/backend/catalog/heap.c                    |   6 +-
 src/backend/commands/amcmds.c                 |  16 ++
 src/backend/commands/sequence.c               |  21 +-
 src/backend/commands/tablecmds.c              |  12 +-
 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_tables.c           |  12 ++
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 src/bin/psql/describe.c                       |   2 +
 src/bin/psql/tab-complete.c                   |   4 +-
 src/test/regress/expected/create_am.out       |  33 ++-
 src/test/regress/expected/opr_sanity.out      |  12 ++
 src/test/regress/expected/psql.out            |  64 +++---
 src/test/regress/sql/create_am.sql            |  24 ++-
 src/test/regress/sql/opr_sanity.sql           |  10 +
 src/tools/pgindent/typedefs.list              |   5 +-
 39 files changed, 675 insertions(+), 158 deletions(-)
 create mode 100644 src/include/access/sequenceam.h
 create mode 100644 src/backend/access/sequence/sequenceamapi.c

diff --git a/src/include/access/localam.h b/src/include/access/localam.h
index 5b0575dc2e..7afc0a9636 100644
--- a/src/include/access/localam.h
+++ b/src/include/access/localam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_LOCAL_SEQ_LOG			0x00
@@ -31,18 +30,4 @@ extern void local_seq_desc(StringInfo buf, XLogReaderState *record);
 extern const char *local_seq_identify(uint8 info);
 extern void local_seq_mask(char *page, BlockNumber blkno);
 
-/* access routines */
-extern int64 local_seq_nextval(Relation rel, int64 incby, int64 maxv,
-							   int64 minv, int64 cache, bool cycle,
-							   int64 *last);
-extern const char *local_seq_get_table_am(void);
-extern void local_seq_init(Relation rel, int64 last_value, bool is_called);
-extern void local_seq_setval(Relation rel, int64 next, bool iscalled);
-extern void local_seq_reset(Relation rel, int64 startv, bool is_called,
-							bool reset_state);
-extern void local_seq_get_state(Relation rel, int64 *last_value,
-								bool *is_called);
-extern void local_seq_change_persistence(Relation rel,
-										 char newrelpersistence);
-
 #endif							/* LOCALAM_H */
diff --git a/src/include/access/sequenceam.h b/src/include/access/sequenceam.h
new file mode 100644
index 0000000000..f0f36c6c7e
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,188 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2023, 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 "local"
+
+/* 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.
+	 */
+	void		(*get_state) (Relation rel, int64 *last_value, bool *is_called);
+
+	/*
+	 * 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)
+{
+	rel->rd_sequenceam->get_state(rel, last_value, is_called);
+}
+
+/*
+ * 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);
+
+/* ----------------------------------------------------------------------------
+ * Functions in local.c
+ * ----------------------------------------------------------------------------
+ */
+
+extern const SequenceAmRoutine *GetLocalSequenceAmRoutine(void);
+
+#endif							/* SEQUENCEAM_H */
diff --git a/src/include/catalog/pg_am.dat b/src/include/catalog/pg_am.dat
index db87490282..3b31f9df59 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 => '8048', oid_symbol => 'LOCAL_SEQUENCE_AM_OID',
+  descr => 'local sequence access method',
+  amname => 'local', amhandler => '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 475593fad4..3a94e8c636 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 3b7533e7bb..b858fa9820 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -217,15 +217,19 @@ MAKE_SYSCACHE(RELNAMENSP, pg_class_relname_nsp_index, 128);
 	 && (relkind) != RELKIND_SEQUENCE)
 
 /*
- * Relation kinds with a table access method (rd_tableam).  Although sequences
- * use the heap table AM, they are enough of a special case in most uses that
- * they are not included here.
+ * Relation kinds with a table access method (rd_tableam).
  */
 #define RELKIND_HAS_TABLE_AM(relkind) \
 	((relkind) == RELKIND_RELATION || \
 	 (relkind) == RELKIND_TOASTVALUE || \
 	 (relkind) == RELKIND_MATVIEW)
 
+/*
+ * Relation kinds with a sequence access method (rd_sequenceam).
+ */
+#define RELKIND_HAS_SEQUENCE_AM(relkind) \
+	((relkind) == RELKIND_SEQUENCE)
+
 extern int	errdetail_relkind_not_supported(char relkind);
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 1b09705f2a..971febffe8 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 => 'local_sequenceam_handler', provolatile => 'v',
+  prorettype => 'sequence_am_handler', proargtypes => 'internal',
+  prosrc => 'local_sequenceam_handler' },
+
 # Index access method handlers
 { oid => '330', descr => 'btree index access method handler',
   proname => 'bthandler', provolatile => 'v', prorettype => 'index_am_handler',
@@ -7613,6 +7619,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 d29194da31..8f00b322ec 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -626,6 +626,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 0c53d67d3e..a3f6fecf27 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -145,6 +145,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 e88cbee3b5..9b22b2f248 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index b665e55b65..774ae2652c 100644
--- a/src/include/nodes/meson.build
+++ b/src/include/nodes/meson.build
@@ -9,6 +9,7 @@ node_support_input_i = [
   '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/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7ce145783d..9461bc9f89 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2962,6 +2962,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 339c490300..0b05cb6f87 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -53,6 +53,8 @@ extern bool check_debug_io_direct(char **newval, void **extra, GucSource source)
 extern void assign_debug_io_direct(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 ab9fa4faf9..378902ad29 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -187,6 +187,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 697f89905e..b89a7e0526 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = local.o sequence.o
+OBJS = local.o sequence.o sequenceamapi.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/sequence/local.c b/src/backend/access/sequence/local.c
index e77f25e13e..2d20abc58e 100644
--- a/src/backend/access/sequence/local.c
+++ b/src/backend/access/sequence/local.c
@@ -18,6 +18,7 @@
 #include "access/bufmask.h"
 #include "access/localam.h"
 #include "access/multixact.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -25,6 +26,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -297,15 +299,15 @@ local_seq_redo(XLogReaderState *record)
 }
 
 /*
- * local_seq_nextval()
+ * local_nextval()
  *
  * Allocate a new value for a local sequence, based on the sequence
  * configuration.
  */
-int64
-local_seq_nextval(Relation rel, int64 incby, int64 maxv,
-				  int64 minv, int64 cache, bool cycle,
-				  int64 *last)
+static int64
+local_nextval(Relation rel, int64 incby, int64 maxv,
+			  int64 minv, int64 cache, bool cycle,
+			  int64 *last)
 {
 	int64		result;
 	int64		fetch;
@@ -485,18 +487,18 @@ local_seq_nextval(Relation rel, int64 incby, int64 maxv,
 }
 
 /*
- * local_seq_get_table_am()
+ * local_get_table_am()
  *
  * Return the table access method used by this sequence.
  */
-const char *
-local_seq_get_table_am(void)
+static const char *
+local_get_table_am(void)
 {
 	return "heap";
 }
 
 /*
- * local_seq_init()
+ * 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
@@ -504,8 +506,8 @@ local_seq_get_table_am(void)
  * inserted after the relation has been created, filling in its heap
  * table.
  */
-void
-local_seq_init(Relation rel, int64 last_value, bool is_called)
+static void
+local_init(Relation rel, int64 last_value, bool is_called)
 {
 	Datum		value[SEQ_COL_LASTCOL];
 	bool		null[SEQ_COL_LASTCOL];
@@ -567,12 +569,12 @@ local_seq_init(Relation rel, int64 last_value, bool is_called)
 }
 
 /*
- * local_seq_setval()
+ * local_setval()
  *
  * Callback for setval().
  */
-void
-local_seq_setval(Relation rel, int64 next, bool iscalled)
+static void
+local_setval(Relation rel, int64 next, bool iscalled)
 {
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
@@ -614,13 +616,13 @@ local_seq_setval(Relation rel, int64 next, bool iscalled)
 }
 
 /*
- * local_seq_reset()
+ * local_reset()
  *
  * Perform a hard reset on the local sequence, rewriting its heap data
  * entirely.
  */
-void
-local_seq_reset(Relation rel, int64 startv, bool is_called, bool reset_state)
+static void
+local_reset(Relation rel, int64 startv, bool is_called, bool reset_state)
 {
 	Form_pg_sequence_data seq;
 	Buffer		buf;
@@ -668,12 +670,12 @@ local_seq_reset(Relation rel, int64 startv, bool is_called, bool reset_state)
 }
 
 /*
- * local_seq_get_state()
+ * local_get_state()
  *
  * Retrieve the state of a local sequence.
  */
-void
-local_seq_get_state(Relation rel, int64 *last_value, bool *is_called)
+static void
+local_get_state(Relation rel, int64 *last_value, bool *is_called)
 {
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
@@ -689,12 +691,12 @@ local_seq_get_state(Relation rel, int64 *last_value, bool *is_called)
 }
 
 /*
- * local_seq_change_persistence()
+ * local_change_persistence()
  *
  * Persistence change for the local sequence Relation.
  */
-void
-local_seq_change_persistence(Relation rel, char newrelpersistence)
+static void
+local_change_persistence(Relation rel, char newrelpersistence)
 {
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
@@ -704,3 +706,24 @@ local_seq_change_persistence(Relation rel, char newrelpersistence)
 	fill_seq_with_data(rel, &seqdatatuple);
 	UnlockReleaseBuffer(buf);
 }
+
+/* ------------------------------------------------------------------------
+ * Definition of the local sequence access method.
+ * ------------------------------------------------------------------------
+ */
+static const SequenceAmRoutine local_methods = {
+	.type = T_SequenceAmRoutine,
+	.get_table_am = local_get_table_am,
+	.init = local_init,
+	.nextval = local_nextval,
+	.setval = local_setval,
+	.reset = local_reset,
+	.get_state = local_get_state,
+	.change_persistence = local_change_persistence
+};
+
+Datum
+local_sequenceam_handler(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_POINTER(&local_methods);
+}
diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build
index 8c4ef42467..9baab19512 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -3,4 +3,5 @@
 backend_sources += files(
   'local.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/sequence.c b/src/backend/access/sequence/sequence.c
index 8d6b7bb5dc..5e7d76d3b0 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 0000000000..4314b9ecb5
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2023, 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/xact.h"
+#include "access/sequenceam.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 348943e36c..feba70af4f 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1454,8 +1454,12 @@ 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.
+		 *
+		 * 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)
+		if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE) ||
+			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 10e386288a..bec772713e 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 f69ee063b2..fc4086ea29 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/localam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -152,6 +152,7 @@ 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;
@@ -173,7 +174,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	rel = sequence_open(seqoid, AccessExclusiveLock);
 
 	/* now initialize the sequence table structure and its data */
-	local_seq_init(rel, last_value, is_called);
+	sequence_init(rel, last_value, is_called);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -241,7 +242,7 @@ ResetSequence(Oid seq_relid)
 	ReleaseSysCache(pgstuple);
 
 	/* Sequence state is forcibly reset here. */
-	local_seq_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 */
@@ -297,7 +298,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
 
 	/* Read sequence data */
-	local_seq_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -310,7 +311,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		if (RelationNeedsWAL(seqrel))
 			GetTopTransactionId();
 
-		local_seq_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 */
@@ -346,7 +347,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence)
 	if (RelationNeedsWAL(seqrel))
 		GetTopTransactionId();
 
-	local_seq_change_persistence(seqrel, newrelpersistence);
+	sequence_change_persistence(seqrel, newrelpersistence);
 
 	sequence_close(seqrel, NoLock);
 }
@@ -463,8 +464,8 @@ nextval_internal(Oid relid, bool check_permissions)
 	ReleaseSysCache(pgstuple);
 
 	/* retrieve next value from the access method */
-	result = local_seq_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;
@@ -618,7 +619,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 		GetTopTransactionId();
 
 	/* Call the access method callback */
-	local_seq_setval(seqrel, next, iscalled);
+	sequence_setval(seqrel, next, iscalled);
 
 	sequence_close(seqrel, NoLock);
 }
@@ -1334,7 +1335,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 				 errmsg("permission denied for sequence %s",
 						RelationGetRelationName(seqrel))));
 
-	local_seq_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 	sequence_close(seqrel, NoLock);
 
 	values[0] = BoolGetDatum(is_called);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ee9dffdf15..cbd6c0de75 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -22,6 +22,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"
@@ -969,10 +970,17 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	}
 	else if (RELKIND_HAS_TABLE_AM(relkind))
 		accessMethod = default_table_access_method;
+	else if (RELKIND_HAS_SEQUENCE_AM(relkind))
+		accessMethod = default_sequence_access_method;
 
-	/* look up the access method, verify it is for a table */
+	/* look up the access method, verify it is for a table or a sequence */
 	if (accessMethod != NULL)
-		accessMethodId = get_table_am_oid(accessMethod, false);
+	{
+		if (RELKIND_HAS_SEQUENCE_AM(relkind))
+			accessMethodId = get_sequence_am_oid(accessMethod, false);
+		else
+			accessMethodId = get_table_am_oid(accessMethod, false);
+	}
 
 	/*
 	 * Create the relation.  Inherited defaults and constraints are passed in
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 66bbad8e6e..86b1773c0b 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -47,6 +47,7 @@ node_headers = \
 	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/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 2f0a59bc87..13b483eafe 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -59,6 +59,7 @@ my @all_input_files = qw(
   nodes/execnodes.h
   access/amapi.h
   access/sdir.h
+  access/sequenceam.h
   access/tableam.h
   access/tsmapi.h
   commands/event_trigger.h
@@ -83,6 +84,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 130f7fc7c3..0d985a6fae 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
@@ -4776,23 +4777,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;
@@ -4829,6 +4833,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); }
 		;
@@ -5825,6 +5834,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 c7efd8d8ce..71169c15bf 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"
@@ -470,6 +471,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence->relpersistence = cxt->rel ? cxt->rel->rd_rel->relpersistence : cxt->relation->relpersistence;
 
 	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 a3a991f634..751d699937 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -372,6 +372,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 50acae4529..290044b5eb 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -35,6 +35,7 @@
 #include "access/nbtree.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"
@@ -66,6 +67,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);
@@ -1207,9 +1210,10 @@ 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 (RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind))
+		RelationInitSequenceAccessMethod(relation);
 	else
 		Assert(relation->rd_rel->relam == InvalidOid);
 
@@ -1806,17 +1810,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.
@@ -1847,6 +1843,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
  *
@@ -3669,14 +3708,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);
 
 	/*
 	 * Okay to insert into the relcache hash table.
@@ -4289,13 +4331,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);
@@ -6301,8 +6351,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);
@@ -6314,6 +6366,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_tables.c b/src/backend/utils/misc/guc_tables.c
index 527a2b2734..49d579eae7 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -29,6 +29,7 @@
 #include "access/commit_ts.h"
 #include "access/gin.h"
 #include "access/toast_compression.h"
+#include "access/sequenceam.h"
 #include "access/twophase.h"
 #include "access/xlog_internal.h"
 #include "access/xlogprefetcher.h"
@@ -4066,6 +4067,17 @@ struct config_string ConfigureNamesString[] =
 		check_default_table_access_method, NULL, NULL
 	},
 
+	{
+		{"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets the default sequence access method for new sequences."),
+			NULL,
+			GUC_IS_NAME
+		},
+		&default_sequence_access_method,
+		DEFAULT_SEQUENCE_ACCESS_METHOD,
+		check_default_sequence_access_method, NULL, NULL
+	},
+
 	{
 		{"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default tablespace to create tables and indexes in."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c97f9a25f0..8c737c7569 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -691,6 +691,7 @@
 					#   error
 #search_path = '"$user", public'	# schema names
 #row_security = on
+#default_sequence_access_method = 'local'
 #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/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index 082cb8a589..1dd822fe44 100644
--- a/src/bin/pg_waldump/t/001_basic.pl
+++ b/src/bin/pg_waldump/t/001_basic.pl
@@ -41,7 +41,7 @@ Btree
 Hash
 Gin
 Gist
-Sequence
+LocalSequence
 SPGist
 BRIN
 CommitTs
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index b6a4eb1d56..685ba1cc9d 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -161,10 +161,12 @@ describeAccessMethods(const char *pattern, bool verbose)
 					  "SELECT amname AS \"%s\",\n"
 					  "  CASE amtype"
 					  " WHEN 'i' THEN '%s'"
+					  " WHEN 's' THEN '%s'"
 					  " WHEN 't' 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.c b/src/bin/psql/tab-complete.c
index 151a5211ee..598eb93073 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2197,7 +2197,7 @@ psql_completion(const char *text, int start, int end)
 	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");
@@ -3217,7 +3217,7 @@ psql_completion(const char *text, int start, int end)
 	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 b50293d514..dfcc9cff49 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;
@@ -331,9 +326,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 = 'local';
 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;
@@ -356,7 +354,7 @@ ORDER BY 3, 1, 2;
  r       | heap2  | tableam_parted_1_heapx
  r       | heap   | tableam_parted_2_heapx
  p       |        | tableam_parted_heapx
- S       |        | tableam_seq_heapx
+ S       | local  | tableam_seq_heapx
  r       | heap2  | tableam_tbl_heapx
  r       | heap2  | tableam_tblas_heapx
  m       | heap2  | tableam_tblmv_heapx
@@ -388,3 +386,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 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 7610b011d6..12f48e4beb 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1929,6 +1929,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 ad02772562..4e621788f5 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4959,8 +4959,8 @@ Indexes:
 -- check printing info about access methods
 \dA
 List of access methods
-  Name  | Type  
---------+-------
+  Name  |   Type   
+--------+----------
  brin   | Index
  btree  | Index
  gin    | Index
@@ -4968,13 +4968,14 @@ List of access methods
  hash   | Index
  heap   | Table
  heap2  | Table
+ local  | Sequence
  spgist | Index
-(8 rows)
+(9 rows)
 
 \dA *
 List of access methods
-  Name  | Type  
---------+-------
+  Name  |   Type   
+--------+----------
  brin   | Index
  btree  | Index
  gin    | Index
@@ -4982,8 +4983,9 @@ List of access methods
  hash   | Index
  heap   | Table
  heap2  | Table
+ local  | Sequence
  spgist | Index
-(8 rows)
+(9 rows)
 
 \dA h*
 List of access methods
@@ -5008,32 +5010,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     | 
+ local  | Sequence | 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     | 
+ local  | Sequence | 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/sql/create_am.sql b/src/test/regress/sql/create_am.sql
index 2785ffd8bb..6b180519aa 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;
@@ -222,9 +219,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 = 'local';
 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;
@@ -257,3 +258,16 @@ CREATE TABLE i_am_a_failure() USING "btree";
 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 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 2fe7b6dcc4..1409622374 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1229,6 +1229,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/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index fc8b15d0cf..5805ccce0d 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2506,6 +2506,7 @@ SeqScan
 SeqScanState
 SeqTable
 SeqTableData
+SequenceAmRoutine
 SerCommitSeqNo
 SerialControl
 SerialIOData
@@ -3482,6 +3483,7 @@ lineno_t
 list_sort_comparator
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 locale_t
 locate_agg_of_level_context
@@ -3746,7 +3748,6 @@ save_buffer
 scram_state
 scram_state_enum
 sem_t
-sequence_magic
 set_join_pathlist_hook_type
 set_rel_pathlist_hook_type
 shm_mq
@@ -3964,6 +3965,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
@@ -3975,7 +3977,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.43.0

v3-0006-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From ccdebdbab1dfbbd8be352888ba668d6bbc7a3653 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v3 6/8] 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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 36a2a5ce43..53231a8ac4 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8790,6 +8790,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 bb4926b887..1dcb86468c 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -95,6 +95,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 2c107199d3..76953fee12 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -257,6 +257,7 @@ break is not needed in a wider output rendering.
   &geqo;
   &tableam;
   &indexam;
+  &sequenceam;
   &generic-wal;
   &custom-rmgr;
   &btree;
diff --git a/doc/src/sgml/ref/create_access_method.sgml b/doc/src/sgml/ref/create_access_method.sgml
index dae43dbaed..3067dc4d4d 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>SEQUENCE</literal> and
+      <literal>INDEX</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 34e9084b5c..d00bdabde0 100644
--- a/doc/src/sgml/ref/create_sequence.sgml
+++ b/doc/src/sgml/ref/create_sequence.sgml
@@ -27,6 +27,7 @@ CREATE [ { TEMPORARY | TEMP } | UNLOGGED ] SEQUENCE [ IF NOT EXISTS ] <replaceab
     [ MINVALUE <replaceable class="parameter">minvalue</replaceable> | NO MINVALUE ] [ MAXVALUE <replaceable class="parameter">maxvalue</replaceable> | NO MAXVALUE ]
     [ START [ WITH ] <replaceable class="parameter">start</replaceable> ] [ CACHE <replaceable class="parameter">cache</replaceable> ] [ [ NO ] CYCLE ]
     [ OWNED BY { <replaceable class="parameter">table_name</replaceable>.<replaceable class="parameter">column_name</replaceable> | NONE } ]
+    [ USING <replaceable class="parameter">access_method</replaceable> ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -261,6 +262,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 0000000000..a96170bfac
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.43.0

v3-0007-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From bb3149953fbfedb8f900bcc7981c57d3674197e6 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:56:26 +0900
Subject: [PATCH v3 7/8] 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            | 39 ++++++++++++++--
 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     | 63 ++++++++++++++++++++++----
 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, 210 insertions(+), 14 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 9ef2f2017e..a838e6490c 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -95,6 +95,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
@@ -183,6 +184,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 d97ebaff5b..d7655f7633 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -171,6 +171,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;
@@ -1118,6 +1119,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->owner = opts->owner ? pg_strdup(opts->owner) : NULL;
 	newToc->desc = pg_strdup(opts->description);
@@ -2258,6 +2260,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 */
 
@@ -2487,6 +2490,7 @@ WriteToc(ArchiveHandle *AH)
 		WriteStr(AH, te->copyStmt);
 		WriteStr(AH, te->namespace);
 		WriteStr(AH, te->tablespace);
+		WriteStr(AH, te->sequenceam);
 		WriteStr(AH, te->tableam);
 		WriteStr(AH, te->owner);
 		WriteStr(AH, "false");
@@ -2590,6 +2594,9 @@ ReadToc(ArchiveHandle *AH)
 		if (AH->version >= K_VERS_1_10)
 			te->tablespace = ReadStr(AH);
 
+		if (AH->version >= K_VERS_1_16)
+			te->sequenceam = ReadStr(AH);
+
 		if (AH->version >= K_VERS_1_14)
 			te->tableam = ReadStr(AH);
 
@@ -3227,6 +3234,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;
 
@@ -3389,6 +3399,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.
  */
@@ -3548,6 +3609,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
 	_becomeOwner(AH, te);
 	_selectOutputSchema(AH, te->namespace);
 	_selectTablespace(AH, te->tablespace);
+	_selectSequenceAccessMethod(AH, te->sequenceam);
 	_selectTableAccessMethod(AH, te->tableam);
 
 	/* Emit header comment for item */
@@ -4004,6 +4066,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);
@@ -4739,6 +4803,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -4788,6 +4853,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 917283fd34..d29a22ac40 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -68,10 +68,11 @@
 #define K_VERS_1_15 MAKE_ARCHIVE_VERSION(1, 15, 0)	/* add
 													 * compression_algorithm
 													 * in header */
+#define K_VERS_1_16 MAKE_ARCHIVE_VERSION(1, 16, 0)	/* add sequenceam */
 
 /* Current archive version number (the format we can output) */
 #define K_VERS_MAJOR 1
-#define K_VERS_MINOR 15
+#define K_VERS_MINOR 16
 #define K_VERS_REV 0
 #define K_VERS_SELF MAKE_ARCHIVE_VERSION(K_VERS_MAJOR, K_VERS_MINOR, K_VERS_REV)
 
@@ -319,6 +320,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 */
 
@@ -347,6 +349,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	   *owner;
 	char	   *desc;
@@ -387,6 +390,7 @@ typedef struct _archiveOpts
 	const char *tag;
 	const char *namespace;
 	const char *tablespace;
+	const char *sequenceam;
 	const char *tableam;
 	const char *owner;
 	const char *description;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4d50a3e336..bf875d2745 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -412,6 +412,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},
@@ -1016,6 +1017,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;
@@ -1131,6 +1133,7 @@ help(const char *progname)
 	printf(_("  --no-publications            do not dump publications\n"));
 	printf(_("  --no-security-labels         do not dump security label assignments\n"));
 	printf(_("  --no-subscriptions           do not dump subscriptions\n"));
+	printf(_("  --no-sequence-access-method  do not 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"));
@@ -13199,6 +13202,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;
@@ -17417,7 +17423,8 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 			   *maxv,
 			   *minv,
 			   *cache,
-			   *seqtype;
+			   *seqtype,
+			   *seqam;
 	bool		cycled;
 	bool		is_ascending;
 	int64		default_minv,
@@ -17431,13 +17438,35 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
-	if (fout->remoteVersion >= 100000)
+	if (fout->remoteVersion >= 170000)
 	{
+		/*
+		 * PostgreSQL 17 has added support for sequence access methods.
+		 */
+		appendPQExpBuffer(query,
+						  "SELECT format_type(s.seqtypid, NULL), "
+						  "s.seqstart, s.seqincrement, "
+						  "s.seqmax, s.seqmin, "
+						  "s.seqcache, s.seqcycle, "
+						  "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) "
+						  "WHERE s.seqrelid = '%u'::oid",
+						  tbinfo->dobj.catId.oid);
+	}
+	else if (fout->remoteVersion >= 100000)
+	{
+		/*
+		 * PostgreSQL 10 has moved sequence metadata to the catalog
+		 * pg_sequence.
+		 */
 		appendPQExpBuffer(query,
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
+						  "seqcache, seqcycle, "
+						  "'local' AS seqam "
 						  "FROM pg_catalog.pg_sequence "
 						  "WHERE seqrelid = '%u'::oid",
 						  tbinfo->dobj.catId.oid);
@@ -17453,7 +17482,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 		appendPQExpBuffer(query,
 						  "SELECT 'bigint' AS sequence_type, "
 						  "start_value, increment_by, max_value, min_value, "
-						  "cache_value, is_cycled FROM %s",
+						  "cache_value, is_cycled, 'local' as seqam FROM %s",
 						  fmtQualifiedDumpable(tbinfo));
 	}
 
@@ -17472,6 +17501,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	minv = PQgetvalue(res, 0, 4);
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
+	seqam = PQgetvalue(res, 0, 7);
 
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
@@ -17603,6 +17633,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 					 ARCHIVE_OPTS(.tag = tbinfo->dobj.name,
 								  .namespace = tbinfo->dobj.namespace->dobj.name,
 								  .owner = tbinfo->rolname,
+								  .sequenceam = 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 491311fe79..8a650f7fba 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -99,6 +99,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;
@@ -163,6 +164,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},
@@ -437,6 +439,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)
@@ -670,6 +674,7 @@ help(void)
 	printf(_("  --no-security-labels         do not dump security label assignments\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 c3beacdec1..0049130535 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -68,6 +68,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;
@@ -115,6 +116,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},
@@ -351,6 +353,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;
@@ -479,6 +482,7 @@ usage(const char *progname)
 	printf(_("  --no-publications            do not restore publications\n"));
 	printf(_("  --no-security-labels         do not restore security labels\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(_("  --section=SECTION            restore named section (pre-data, data, or post-data)\n"));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 00b5092713..f0d69fa6ac 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -548,6 +548,13 @@ 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', 'postgres',
+		],
+	},
 	no_table_access_method => {
 		dump_cmd => [
 			'pg_dump', '--no-sync',
@@ -722,6 +729,7 @@ my %full_runs = (
 	no_large_objects => 1,
 	no_owner => 1,
 	no_privs => 1,
+	no_sequence_access_method => 1,
 	no_table_access_method => 1,
 	pg_dumpall_dbprivs => 1,
 	pg_dumpall_exclude => 1,
@@ -3866,9 +3874,7 @@ my %tests = (
 		\QCREATE INDEX measurement_city_id_logdate_idx ON ONLY dump_test.measurement USING\E
 		/xm,
 		like => {
-			%full_runs,
-			%dump_test_schema_runs,
-			section_post_data => 1,
+			%full_runs, %dump_test_schema_runs, section_post_data => 1,
 		},
 		unlike => {
 			exclude_dump_test_schema => 1,
@@ -4537,6 +4543,18 @@ my %tests = (
 		},
 	},
 
+	'CREATE ACCESS METHOD regress_test_sequence_am' => {
+		create_order => 11,
+		create_sql =>
+		  'CREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER local_sequenceam_handler;',
+		regexp => qr/^
+			\QCREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER 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
@@ -4565,6 +4583,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 local;
+			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 local;',
+		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 => '
@@ -4759,10 +4806,8 @@ $node->command_fails_like(
 ##############################################################
 # Test dumping pg_catalog (for research -- cannot be reloaded)
 
-$node->command_ok(
-	[ 'pg_dump', '-p', "$port", '-n', 'pg_catalog' ],
-	'pg_dump: option -n pg_catalog'
-);
+$node->command_ok([ 'pg_dump', '-p', "$port", '-n', 'pg_catalog' ],
+	'pg_dump: option -n pg_catalog');
 
 #########################################
 # Test valid database exclusion patterns
@@ -4924,8 +4969,8 @@ foreach my $run (sort keys %pgdump_runs)
 		}
 		# Check for useless entries in "unlike" list.  Runs that are
 		# not listed in "like" don't need to be excluded in "unlike".
-		if ($tests{$test}->{unlike}->{$test_key} &&
-			!defined($tests{$test}->{like}->{$test_key}))
+		if ($tests{$test}->{unlike}->{$test_key}
+			&& !defined($tests{$test}->{like}->{$test_key}))
 		{
 			die "useless \"unlike\" entry \"$test_key\" in test \"$test\"";
 		}
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 0caf56e0e0..2cc041ae8c 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1083,6 +1083,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 4d7c046468..34643175fb 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -479,6 +479,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 1a23874da6..6581cff721 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -733,6 +733,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.43.0

v3-0008-dummy_sequence_am-Example-of-sequence-AM.patchtext/x-diff; charset=us-asciiDownload
From cfe3e1d5a483bbb2e9cdfee619a8f2da772c886a Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:56:52 +0900
Subject: [PATCH v3 8/8] dummy_sequence_am: Example of sequence AM

---
 src/test/modules/Makefile                     |   1 +
 src/test/modules/dummy_sequence_am/.gitignore |   3 +
 src/test/modules/dummy_sequence_am/Makefile   |  19 +++
 .../dummy_sequence_am--1.0.sql                |  13 +++
 .../dummy_sequence_am/dummy_sequence_am.c     | 110 ++++++++++++++++++
 .../dummy_sequence_am.control                 |   5 +
 .../expected/dummy_sequence.out               |  35 ++++++
 .../modules/dummy_sequence_am/meson.build     |  33 ++++++
 .../dummy_sequence_am/sql/dummy_sequence.sql  |  17 +++
 src/test/modules/meson.build                  |   1 +
 10 files changed, 237 insertions(+)
 create mode 100644 src/test/modules/dummy_sequence_am/.gitignore
 create mode 100644 src/test/modules/dummy_sequence_am/Makefile
 create mode 100644 src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql
 create mode 100644 src/test/modules/dummy_sequence_am/dummy_sequence_am.c
 create mode 100644 src/test/modules/dummy_sequence_am/dummy_sequence_am.control
 create mode 100644 src/test/modules/dummy_sequence_am/expected/dummy_sequence.out
 create mode 100644 src/test/modules/dummy_sequence_am/meson.build
 create mode 100644 src/test/modules/dummy_sequence_am/sql/dummy_sequence.sql

diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 89aa41b5e3..9789235857 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -10,6 +10,7 @@ SUBDIRS = \
 		  delay_execution \
 		  dummy_index_am \
 		  dummy_seclabel \
+		  dummy_sequence_am \
 		  libpq_pipeline \
 		  plsample \
 		  spgist_name_ops \
diff --git a/src/test/modules/dummy_sequence_am/.gitignore b/src/test/modules/dummy_sequence_am/.gitignore
new file mode 100644
index 0000000000..44d119cfcc
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/.gitignore
@@ -0,0 +1,3 @@
+# Generated subdirectories
+/log/
+/results/
diff --git a/src/test/modules/dummy_sequence_am/Makefile b/src/test/modules/dummy_sequence_am/Makefile
new file mode 100644
index 0000000000..391f7ac946
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/Makefile
@@ -0,0 +1,19 @@
+# src/test/modules/dummy_sequence_am/Makefile
+
+MODULES = dummy_sequence_am
+
+EXTENSION = dummy_sequence_am
+DATA = dummy_sequence_am--1.0.sql
+
+REGRESS = dummy_sequence
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/dummy_sequence_am
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql b/src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql
new file mode 100644
index 0000000000..e12b1f9d87
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql
@@ -0,0 +1,13 @@
+/* src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION dummy_sequence_am" to load this file. \quit
+
+CREATE FUNCTION dummy_sequenceam_handler(internal)
+  RETURNS sequence_am_handler
+  AS 'MODULE_PATHNAME'
+  LANGUAGE C;
+
+CREATE ACCESS METHOD dummy_sequence_am
+  TYPE SEQUENCE HANDLER dummy_sequenceam_handler;
+COMMENT ON ACCESS METHOD dummy_sequence_am IS 'dummy sequence access method';
diff --git a/src/test/modules/dummy_sequence_am/dummy_sequence_am.c b/src/test/modules/dummy_sequence_am/dummy_sequence_am.c
new file mode 100644
index 0000000000..b5ee5d89da
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/dummy_sequence_am.c
@@ -0,0 +1,110 @@
+/*-------------------------------------------------------------------------
+ *
+ * dummy_sequence_am.c
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/test/modules/dummy_sequence_am/dummy_sequence_am.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/sequenceam.h"
+#include "fmgr.h"
+
+PG_MODULE_MAGIC;
+
+/* this sequence is fully on-memory */
+static int	dummy_seqam_last_value = 1;
+static bool dummy_seqam_is_called = false;
+
+PG_FUNCTION_INFO_V1(dummy_sequenceam_handler);
+
+
+/* ------------------------------------------------------------------------
+ * Callbacks for the dummy 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 *
+dummy_sequenceam_get_table_am(void)
+{
+	return "heap";
+}
+
+static void
+dummy_sequenceam_init(Relation rel, int64 last_value, bool is_called)
+{
+	dummy_seqam_last_value = last_value;
+	dummy_seqam_is_called = is_called;
+}
+
+static int64
+dummy_sequenceam_nextval(Relation rel, int64 incby, int64 maxv,
+						 int64 minv, int64 cache, bool cycle,
+						 int64 *last)
+{
+	dummy_seqam_last_value += incby;
+	dummy_seqam_is_called = true;
+
+	return dummy_seqam_last_value;
+}
+
+static void
+dummy_sequenceam_setval(Relation rel, int64 next, bool iscalled)
+{
+	dummy_seqam_last_value = next;
+	dummy_seqam_is_called = iscalled;
+}
+
+static void
+dummy_sequenceam_get_state(Relation rel, int64 *last_value, bool *is_called)
+{
+	*last_value = dummy_seqam_last_value;
+	*is_called = dummy_seqam_is_called;
+}
+
+static void
+dummy_sequenceam_reset(Relation rel, int64 startv, bool is_called,
+					   bool reset_state)
+{
+	dummy_seqam_last_value = startv;
+	dummy_seqam_is_called = is_called;
+}
+
+static void
+dummy_sequenceam_change_persistence(Relation rel, char newrelpersistence)
+{
+	/* nothing to do, really */
+}
+
+/* ------------------------------------------------------------------------
+ * Definition of the dummy sequence access method.
+ * ------------------------------------------------------------------------
+ */
+
+static const SequenceAmRoutine dummy_sequenceam_methods = {
+	.type = T_SequenceAmRoutine,
+	.get_table_am = dummy_sequenceam_get_table_am,
+	.init = dummy_sequenceam_init,
+	.nextval = dummy_sequenceam_nextval,
+	.setval = dummy_sequenceam_setval,
+	.get_state = dummy_sequenceam_get_state,
+	.reset = dummy_sequenceam_reset,
+	.change_persistence = dummy_sequenceam_change_persistence
+};
+
+Datum
+dummy_sequenceam_handler(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_POINTER(&dummy_sequenceam_methods);
+}
diff --git a/src/test/modules/dummy_sequence_am/dummy_sequence_am.control b/src/test/modules/dummy_sequence_am/dummy_sequence_am.control
new file mode 100644
index 0000000000..9f10622f2f
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/dummy_sequence_am.control
@@ -0,0 +1,5 @@
+# dummy_sequence_am extension
+comment = 'dummy_sequence_am - sequence access method template'
+default_version = '1.0'
+module_pathname = '$libdir/dummy_sequence_am'
+relocatable = true
diff --git a/src/test/modules/dummy_sequence_am/expected/dummy_sequence.out b/src/test/modules/dummy_sequence_am/expected/dummy_sequence.out
new file mode 100644
index 0000000000..57588cea5b
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/expected/dummy_sequence.out
@@ -0,0 +1,35 @@
+CREATE EXTENSION dummy_sequence_am;
+CREATE SEQUENCE dummyseq USING dummy_sequence_am;
+SELECT nextval('dummyseq'::regclass);
+ nextval 
+---------
+       2
+(1 row)
+
+SELECT setval('dummyseq'::regclass, 14);
+ setval 
+--------
+     14
+(1 row)
+
+SELECT nextval('dummyseq'::regclass);
+ nextval 
+---------
+      15
+(1 row)
+
+-- Sequence relation exists, but it has no attributes.
+SELECT * FROM dummyseq;
+--
+(0 rows)
+
+-- Reset connection, which will reset the sequence
+\c
+SELECT nextval('dummyseq'::regclass);
+ nextval 
+---------
+       2
+(1 row)
+
+DROP SEQUENCE dummyseq;
+DROP EXTENSION dummy_sequence_am;
diff --git a/src/test/modules/dummy_sequence_am/meson.build b/src/test/modules/dummy_sequence_am/meson.build
new file mode 100644
index 0000000000..84460070e4
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+dummy_sequence_am_sources = files(
+  'dummy_sequence_am.c',
+)
+
+if host_system == 'windows'
+  dummy_sequence_am_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dummy_sequence_am',
+    '--FILEDESC', 'dummy_sequence_am - sequence access method template',])
+endif
+
+dummy_sequence_am = shared_module('dummy_sequence_am',
+  dummy_sequence_am_sources,
+  kwargs: pg_test_mod_args,
+)
+test_install_libs += dummy_sequence_am
+
+test_install_data += files(
+  'dummy_sequence_am.control',
+  'dummy_sequence_am--1.0.sql',
+)
+
+tests += {
+  'name': 'dummy_sequence_am',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'regress': {
+    'sql': [
+      'dummy_sequence',
+    ],
+  },
+}
diff --git a/src/test/modules/dummy_sequence_am/sql/dummy_sequence.sql b/src/test/modules/dummy_sequence_am/sql/dummy_sequence.sql
new file mode 100644
index 0000000000..c739b29a46
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/sql/dummy_sequence.sql
@@ -0,0 +1,17 @@
+CREATE EXTENSION dummy_sequence_am;
+
+CREATE SEQUENCE dummyseq USING dummy_sequence_am;
+
+SELECT nextval('dummyseq'::regclass);
+SELECT setval('dummyseq'::regclass, 14);
+SELECT nextval('dummyseq'::regclass);
+
+-- Sequence relation exists, but it has no attributes.
+SELECT * FROM dummyseq;
+
+-- Reset connection, which will reset the sequence
+\c
+SELECT nextval('dummyseq'::regclass);
+
+DROP SEQUENCE dummyseq;
+DROP EXTENSION dummy_sequence_am;
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 8fbe742d38..f5f16aaff2 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -5,6 +5,7 @@ subdir('commit_ts')
 subdir('delay_execution')
 subdir('dummy_index_am')
 subdir('dummy_seclabel')
+subdir('dummy_sequence_am')
 subdir('gin')
 subdir('injection_points')
 subdir('ldap_password_func')
-- 
2.43.0

#17Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#16)
Re: Sequence Access Methods, round two

On Tue, Feb 27, 2024 at 10:27:13AM +0900, Michael Paquier wrote:

On Thu, Feb 22, 2024 at 05:36:00PM +0100, Tomas Vondra wrote:

0001
------

I think this bit in pg_proc.dat is not quite right:

proallargtypes => '{regclass,bool,int8}', proargmodes => '{i,o,o}',
proargnames => '{seqname,is_called,last_value}',

the first argument should not be "seqname" but rather "seqid".

Ah, right. There are not many system functions that use regclass as
arguments, but the existing ones refer more to IDs, not names.

This patch set is not going to be merged for this release, so I am
going to move it to the next commit fest to continue the discussion in
v18~.

Anyway, there is one piece of this patch set that I think has a lot of
value outside of the discussion with access methods, which is to
redesign pg_sequence_last_value so as it returns a (last_value,
is_called) tuple rather than a (last_value).  This has the benefit of
switching pg_dump to use this function rather than relying on a scan
of the heap table used by a sequence to retrieve the state of a
sequence dumped.  This is the main diff:
-    appendPQExpBuffer(query,
-                      "SELECT last_value, is_called FROM %s",
-                      fmtQualifiedDumpable(tbinfo));
+    /*
+     * In versions 17 and up, pg_sequence_last_value() has been switched to
+     * return a tuple with last_value and is_called.
+     */
+    if (fout->remoteVersion >= 170000)
+        appendPQExpBuffer(query,
+                          "SELECT last_value, is_called "
+                          "FROM pg_sequence_last_value('%s')",
+                          fmtQualifiedDumpable(tbinfo));
+    else
+        appendPQExpBuffer(query,
+                          "SELECT last_value, is_called FROM %s",
+                          fmtQualifiedDumpable(tbinfo));

Are there any objections to that? pg_sequence_last_value() is
something that we've only been relying on internally for the catalog
pg_sequences.
--
Michael

#18Peter Eisentraut
peter@eisentraut.org
In reply to: Michael Paquier (#17)
Re: Sequence Access Methods, round two

On 12.03.24 00:44, Michael Paquier wrote:

Anyway, there is one piece of this patch set that I think has a lot of
value outside of the discussion with access methods, which is to
redesign pg_sequence_last_value so as it returns a (last_value,
is_called) tuple rather than a (last_value).  This has the benefit of
switching pg_dump to use this function rather than relying on a scan
of the heap table used by a sequence to retrieve the state of a
sequence dumped.  This is the main diff:
-    appendPQExpBuffer(query,
-                      "SELECT last_value, is_called FROM %s",
-                      fmtQualifiedDumpable(tbinfo));
+    /*
+     * In versions 17 and up, pg_sequence_last_value() has been switched to
+     * return a tuple with last_value and is_called.
+     */
+    if (fout->remoteVersion >= 170000)
+        appendPQExpBuffer(query,
+                          "SELECT last_value, is_called "
+                          "FROM pg_sequence_last_value('%s')",
+                          fmtQualifiedDumpable(tbinfo));
+    else
+        appendPQExpBuffer(query,
+                          "SELECT last_value, is_called FROM %s",
+                          fmtQualifiedDumpable(tbinfo));

Are there any objections to that? pg_sequence_last_value() is
something that we've only been relying on internally for the catalog
pg_sequences.

I don't understand what the overall benefit of this change is supposed
to be.

If this route were to be pursued, it should be a different function
name. We shouldn't change the signature of an existing function.

#19Michael Paquier
michael@paquier.xyz
In reply to: Peter Eisentraut (#18)
Re: Sequence Access Methods, round two

On Wed, Mar 13, 2024 at 07:00:37AM +0100, Peter Eisentraut wrote:

I don't understand what the overall benefit of this change is supposed to
be.

In the context of this thread, this removes the dependency of sequence
value lookup to heap.

If this route were to be pursued, it should be a different function name.
We shouldn't change the signature of an existing function.

I'm not so sure about that. The existing pg_sequence_last_value is
undocumented and only used in a system view.
--
Michael

#20Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#19)
Re: Sequence Access Methods, round two

On Thu, Mar 14, 2024 at 09:40:29AM +0900, Michael Paquier wrote:

In the context of this thread, this removes the dependency of sequence
value lookup to heap.

I am not sure where this is leading in combination with the sequence
stuff for logical decoding, so for now I am moving this patch to the
next commit fest to discuss things in 18~.
--
Michael

#21Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#20)
8 attachment(s)
Re: Sequence Access Methods, round two

On Tue, Mar 19, 2024 at 10:54:41AM +0900, Michael Paquier wrote:

I am not sure where this is leading in combination with the sequence
stuff for logical decoding, so for now I am moving this patch to the
next commit fest to discuss things in 18~.

I have plans to rework this patch set for the next commit fest,
and this includes some investigation about custom data types that
could be plugged into these AMs. For now, please find a rebase as
there were a couple of conflicts.
--
Michael

Attachments:

v4-0001-Switch-pg_sequence_last_value-to-report-a-tuple-a.patchtext/x-diff; charset=us-asciiDownload
From fa5bb6f0ff4c99a9cc7408fe0dbab62f7ad16df2 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 27 Feb 2024 09:02:57 +0900
Subject: [PATCH v4 1/8] Switch pg_sequence_last_value() to report a tuple and
 use it in pg_dump

This commit switches pg_sequence_last_value() to report a tuple made of
(last_value,is_called) that can be directly be used for the arguments of
setval() in a sequence.

Going forward with PostgreSQL 17, pg_dump and pg_sequences make use of
it instead of scanning the heap table assumed to always exist for a
sequence.

Note: this requires a catversion bump.
---
 src/include/catalog/pg_proc.dat      |  6 ++++--
 src/backend/catalog/system_views.sql |  6 +++++-
 src/backend/commands/sequence.c      | 19 +++++++++++++------
 src/bin/pg_dump/pg_dump.c            | 16 +++++++++++++---
 src/test/regress/expected/rules.out  |  7 ++++++-
 5 files changed, 41 insertions(+), 13 deletions(-)

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 134e3b22fd..1a8c8ebba5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3325,9 +3325,11 @@
   proargmodes => '{i,o,o,o,o,o,o,o}',
   proargnames => '{sequence_oid,start_value,minimum_value,maximum_value,increment,cycle_option,cache_size,data_type}',
   prosrc => 'pg_sequence_parameters' },
-{ oid => '4032', descr => 'sequence last value',
+{ oid => '4032', descr => 'sequence last value data',
   proname => 'pg_sequence_last_value', provolatile => 'v', proparallel => 'u',
-  prorettype => 'int8', proargtypes => 'regclass',
+  prorettype => 'record', proargtypes => 'regclass',
+  proallargtypes => '{regclass,bool,int8}', proargmodes => '{i,o,o}',
+  proargnames => '{seqid,is_called,last_value}',
   prosrc => 'pg_sequence_last_value' },
 
 { oid => '275', descr => 'return the next oid for a system table',
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2e61f6d74e..0dfb6346ea 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -178,7 +178,11 @@ CREATE VIEW pg_sequences AS
         S.seqcache AS cache_size,
         CASE
             WHEN has_sequence_privilege(C.oid, 'SELECT,USAGE'::text)
-                THEN pg_sequence_last_value(C.oid)
+                THEN (SELECT
+                          CASE WHEN sl.is_called
+                              THEN sl.last_value ELSE NULL
+                          END
+                      FROM pg_sequence_last_value(C.oid) sl)
             ELSE NULL
         END AS last_value
     FROM pg_sequence S JOIN pg_class C ON (C.oid = S.seqrelid)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 46103561c3..9646c72228 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1774,14 +1774,22 @@ pg_sequence_parameters(PG_FUNCTION_ARGS)
 Datum
 pg_sequence_last_value(PG_FUNCTION_ARGS)
 {
+#define PG_SEQUENCE_LAST_VALUE_COLS		2
 	Oid			relid = PG_GETARG_OID(0);
+	Datum		values[PG_SEQUENCE_LAST_VALUE_COLS] = {0};
+	bool		nulls[PG_SEQUENCE_LAST_VALUE_COLS] = {0};
 	SeqTable	elm;
 	Relation	seqrel;
+	TupleDesc	tupdesc;
 	Buffer		buf;
 	HeapTupleData seqtuple;
 	Form_pg_sequence_data seq;
 	bool		is_called;
-	int64		result;
+	int64		last_value;
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	/* open and lock sequence */
 	init_sequence(relid, &elm, &seqrel);
@@ -1795,15 +1803,14 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	seq = read_seq_tuple(seqrel, &buf, &seqtuple);
 
 	is_called = seq->is_called;
-	result = seq->last_value;
+	last_value = seq->last_value;
 
 	UnlockReleaseBuffer(buf);
 	sequence_close(seqrel, NoLock);
 
-	if (is_called)
-		PG_RETURN_INT64(result);
-	else
-		PG_RETURN_NULL();
+	values[0] = BoolGetDatum(is_called);
+	values[1] = Int64GetDatum(last_value);
+	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
 }
 
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 6d2f3fdef3..851d228f79 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -17812,9 +17812,19 @@ dumpSequenceData(Archive *fout, const TableDataInfo *tdinfo)
 	bool		called;
 	PQExpBuffer query = createPQExpBuffer();
 
-	appendPQExpBuffer(query,
-					  "SELECT last_value, is_called FROM %s",
-					  fmtQualifiedDumpable(tbinfo));
+	/*
+	 * In versions 17 and up, pg_sequence_last_value() has been switched to
+	 * return a tuple with last_value and is_called.
+	 */
+	if (fout->remoteVersion >= 170000)
+		appendPQExpBuffer(query,
+						  "SELECT last_value, is_called "
+						  "FROM pg_sequence_last_value('%s')",
+						  fmtQualifiedDumpable(tbinfo));
+	else
+		appendPQExpBuffer(query,
+						  "SELECT last_value, is_called FROM %s",
+						  fmtQualifiedDumpable(tbinfo));
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index f4a0f36377..096ac5110b 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1700,7 +1700,12 @@ pg_sequences| SELECT n.nspname AS schemaname,
     s.seqcycle AS cycle,
     s.seqcache AS cache_size,
         CASE
-            WHEN has_sequence_privilege(c.oid, 'SELECT,USAGE'::text) THEN pg_sequence_last_value((c.oid)::regclass)
+            WHEN has_sequence_privilege(c.oid, 'SELECT,USAGE'::text) THEN ( SELECT
+                    CASE
+                        WHEN sl.is_called THEN sl.last_value
+                        ELSE NULL::bigint
+                    END AS "case"
+               FROM pg_sequence_last_value((c.oid)::regclass) sl(is_called, last_value))
             ELSE NULL::bigint
         END AS last_value
    FROM ((pg_sequence s
-- 
2.43.0

v4-0002-Remove-FormData_pg_sequence_data-from-init_params.patchtext/x-diff; charset=us-asciiDownload
From b87140f8842948352bdd97e9ff0cc6a28bbe0ff3 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:00:45 +0900
Subject: [PATCH v4 2/8] Remove FormData_pg_sequence_data from
 init_params()/sequence.c

init_params() sets up "last_value" and "is_called" for a sequence, based
on the sequence properties in pg_sequences.  This simplifies the logic
around log_cnt, which is reset to 0 when the metadata of a sequence is
expected to start from afresh when its properties are updated.
---
 src/backend/commands/sequence.c | 81 ++++++++++++++++++++-------------
 1 file changed, 49 insertions(+), 32 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 9646c72228..98a28dfb45 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -106,7 +106,9 @@ static Form_pg_sequence_data read_seq_tuple(Relation rel,
 static void init_params(ParseState *pstate, List *options, bool for_identity,
 						bool isInit,
 						Form_pg_sequence seqform,
-						Form_pg_sequence_data seqdataform,
+						int64 *last_value,
+						bool *reset_state,
+						bool *is_called,
 						bool *need_seq_rewrite,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
@@ -121,7 +123,9 @@ ObjectAddress
 DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 {
 	FormData_pg_sequence seqform;
-	FormData_pg_sequence_data seqdataform;
+	int64		last_value;
+	bool		reset_state;
+	bool		is_called;
 	bool		need_seq_rewrite;
 	List	   *owned_by;
 	CreateStmt *stmt = makeNode(CreateStmt);
@@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* Check and set all option values */
 	init_params(pstate, seq->options, seq->for_identity, true,
-				&seqform, &seqdataform,
+				&seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/*
@@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 		{
 			case SEQ_COL_LASTVAL:
 				coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
-				value[i - 1] = Int64GetDatumFast(seqdataform.last_value);
+				value[i - 1] = Int64GetDatumFast(last_value);
 				break;
 			case SEQ_COL_LOG:
 				coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
@@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	ObjectAddress address;
 	Relation	rel;
 	HeapTuple	seqtuple;
+	bool		reset_state = false;
+	bool		is_called;
+	int64		last_value;
 	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
@@ -481,12 +488,14 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	/* 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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
-				seqform, newdataform,
+				seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/* If needed, rewrite the sequence relation itself */
@@ -513,6 +522,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * 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);
 	}
 
@@ -1229,17 +1242,19 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 /*
  * init_params: process the options list of CREATE or ALTER SEQUENCE, and
  * store the values into appropriate fields of seqform, for changes that go
- * into the pg_sequence catalog, and fields of seqdataform for changes to the
- * sequence relation itself.  Set *need_seq_rewrite to true if we changed any
- * parameters that require rewriting the sequence's relation (interesting for
- * ALTER SEQUENCE).  Also set *owned_by to any OWNED BY option, or to NIL if
- * there is none.
+ * into the pg_sequence catalog, and fields for changes to the sequence
+ * relation itself (is_called, last_value or any state it may hold).  Set
+ * *need_seq_rewrite to true if we changed any parameters that require
+ * rewriting the sequence's relation (interesting for ALTER SEQUENCE).  Also
+ * set *owned_by to any OWNED BY option, or to NIL if there is none.  Set
+ * *reset_state if the internal state of the sequence needs to change on a
+ * follow-up nextval().
  *
  * If isInit is true, fill any unspecified options with default values;
  * otherwise, do not change existing options that aren't explicitly overridden.
  *
  * Note: we force a sequence rewrite whenever we change parameters that affect
- * generation of future sequence values, even if the seqdataform per se is not
+ * generation of future sequence values, even if the metadata per se is not
  * changed.  This allows ALTER SEQUENCE to behave transactionally.  Currently,
  * the only option that doesn't cause that is OWNED BY.  It's *necessary* for
  * ALTER SEQUENCE OWNED BY to not rewrite the sequence, because that would
@@ -1250,7 +1265,9 @@ static void
 init_params(ParseState *pstate, List *options, bool for_identity,
 			bool isInit,
 			Form_pg_sequence seqform,
-			Form_pg_sequence_data seqdataform,
+			int64 *last_value,
+			bool *reset_state,
+			bool *is_called,
 			bool *need_seq_rewrite,
 			List **owned_by)
 {
@@ -1353,11 +1370,11 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	}
 
 	/*
-	 * We must reset log_cnt when isInit or when changing any parameters that
-	 * would affect future nextval allocations.
+	 * We must reset the state when isInit or when changing any parameters
+	 * that would affect future nextval allocations.
 	 */
 	if (isInit)
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 
 	/* AS type */
 	if (as_type != NULL)
@@ -1406,7 +1423,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("INCREMENT must not be zero")));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1418,7 +1435,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	{
 		seqform->seqcycle = boolVal(is_cycled->arg);
 		Assert(BoolIsValid(seqform->seqcycle));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1429,7 +1446,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (max_value != NULL && max_value->arg)
 	{
 		seqform->seqmax = defGetInt64(max_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || max_value != NULL || reset_max_value)
 	{
@@ -1445,7 +1462,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmax = -1;	/* descending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate maximum value.  No need to check INT8 as seqmax is an int64 */
@@ -1461,7 +1478,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (min_value != NULL && min_value->arg)
 	{
 		seqform->seqmin = defGetInt64(min_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || min_value != NULL || reset_min_value)
 	{
@@ -1477,7 +1494,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmin = 1;	/* ascending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate minimum value.  No need to check INT8 as seqmin is an int64 */
@@ -1528,30 +1545,30 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (restart_value != NULL)
 	{
 		if (restart_value->arg != NULL)
-			seqdataform->last_value = defGetInt64(restart_value);
+			*last_value = defGetInt64(restart_value);
 		else
-			seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
-		seqdataform->log_cnt = 0;
+			*last_value = seqform->seqstart;
+		*is_called = false;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-		seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
+		*last_value = seqform->seqstart;
+		*is_called = false;
 	}
 
 	/* crosscheck RESTART (or current value, if changing MIN/MAX) */
-	if (seqdataform->last_value < seqform->seqmin)
+	if (*last_value < seqform->seqmin)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be less than MINVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmin)));
-	if (seqdataform->last_value > seqform->seqmax)
+	if (*last_value > seqform->seqmax)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be greater than MAXVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmax)));
 
 	/* CACHE */
@@ -1563,7 +1580,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("CACHE (%lld) must be greater than zero",
 							(long long) seqform->seqcache)));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-- 
2.43.0

v4-0003-Integrate-addition-of-attributes-for-sequences-wi.patchtext/x-diff; charset=us-asciiDownload
From 506c2fb7cf6e1ae2b0f627c8e30cbde9a02297ec Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v4 3/8] 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               | 29 +++++++++++++++++--
 src/backend/commands/tablecmds.c              | 10 +++++++
 src/backend/tcop/utility.c                    |  4 +++
 .../test_ddl_deparse/expected/alter_table.out | 10 +++++--
 .../expected/create_sequence_1.out            |  5 +++-
 .../expected/create_table.out                 | 15 ++++++++--
 .../test_ddl_deparse/test_ddl_deparse.c       |  3 ++
 8 files changed, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index c5f34efe27..cfe73cd737 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2353,6 +2353,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 98a28dfb45..d6f2fc3ce0 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -136,6 +136,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;
@@ -174,7 +177,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;
@@ -198,7 +200,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;
@@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 fbffaef196..ea63412431 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4554,6 +4554,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 */
@@ -4857,6 +4858,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,
@@ -5284,6 +5292,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,
@@ -6464,6 +6473,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 fa66b8017e..b64acfb23f 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1664,7 +1664,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 b5e71af9aa..7ebd85200f 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
@@ -71,7 +74,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_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
index 5837ea484e..310ce5a6ba 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.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 75b62aff4d..69e54358ee 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 alter table, tag ALTER TABLE
 NOTICE:    subcommand: type SET ATTNOTNULL desc <NULL>
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 265ef2a547..4be07bcbc8 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -114,6 +114,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.43.0

v4-0004-Move-code-for-local-sequences-to-own-file.patchtext/x-diff; charset=us-asciiDownload
From 113c32eef9eef84599c0b9c9909791a4814b62b3 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:01:55 +0900
Subject: [PATCH v4 4/8] Move code for local sequences to own file

Now that the separation between the in-core sequence computations and
the catalog layer is clean, this moves the code corresponding to the
"local" sequence AM into its own file, out of sequence.c.  The WAL
routines related to sequence are moved in it as well.
---
 src/include/access/localam.h                  |  48 ++
 src/include/access/rmgrlist.h                 |   2 +-
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 .../rmgrdesc/{seqdesc.c => localseqdesc.c}    |  18 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/local.c           | 706 ++++++++++++++++++
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 619 +--------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 12 files changed, 793 insertions(+), 611 deletions(-)
 create mode 100644 src/include/access/localam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => localseqdesc.c} (69%)
 create mode 100644 src/backend/access/sequence/local.c

diff --git a/src/include/access/localam.h b/src/include/access/localam.h
new file mode 100644
index 0000000000..5b0575dc2e
--- /dev/null
+++ b/src/include/access/localam.h
@@ -0,0 +1,48 @@
+/*-------------------------------------------------------------------------
+ *
+ * localam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/localam.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef LOCALAM_H
+#define LOCALAM_H
+
+#include "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_LOCAL_SEQ_LOG			0x00
+
+typedef struct xl_local_seq_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_local_seq_rec;
+
+extern void local_seq_redo(XLogReaderState *record);
+extern void local_seq_desc(StringInfo buf, XLogReaderState *record);
+extern const char *local_seq_identify(uint8 info);
+extern void local_seq_mask(char *page, BlockNumber blkno);
+
+/* access routines */
+extern int64 local_seq_nextval(Relation rel, int64 incby, int64 maxv,
+							   int64 minv, int64 cache, bool cycle,
+							   int64 *last);
+extern const char *local_seq_get_table_am(void);
+extern void local_seq_init(Relation rel, int64 last_value, bool is_called);
+extern void local_seq_setval(Relation rel, int64 next, bool iscalled);
+extern void local_seq_reset(Relation rel, int64 startv, bool is_called,
+							bool reset_state);
+extern void local_seq_get_state(Relation rel, int64 *last_value,
+								bool *is_called);
+extern void local_seq_change_persistence(Relation rel,
+										 char newrelpersistence);
+
+#endif							/* LOCALAM_H */
diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 78e6b908c6..46fd63ae47 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_LOCAL_SEQ_ID, "LocalSequence", local_seq_redo, local_seq_desc, local_seq_identify, NULL, NULL, local_seq_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/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile
index cd95eec37f..dff5a60e68 100644
--- a/src/backend/access/rmgrdesc/Makefile
+++ b/src/backend/access/rmgrdesc/Makefile
@@ -18,13 +18,13 @@ OBJS = \
 	gistdesc.o \
 	hashdesc.o \
 	heapdesc.o \
+	localseqdesc.o \
 	logicalmsgdesc.o \
 	mxactdesc.o \
 	nbtdesc.o \
 	relmapdesc.o \
 	replorigindesc.o \
 	rmgrdesc_utils.o \
-	seqdesc.o \
 	smgrdesc.o \
 	spgdesc.o \
 	standbydesc.o \
diff --git a/src/backend/access/rmgrdesc/seqdesc.c b/src/backend/access/rmgrdesc/localseqdesc.c
similarity index 69%
rename from src/backend/access/rmgrdesc/seqdesc.c
rename to src/backend/access/rmgrdesc/localseqdesc.c
index cf0e02ded5..17b8b71093 100644
--- a/src/backend/access/rmgrdesc/seqdesc.c
+++ b/src/backend/access/rmgrdesc/localseqdesc.c
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
- * seqdesc.c
- *	  rmgr descriptor routines for commands/sequence.c
+ * localseqdesc.c
+ *	  rmgr descriptor routines for sequence/local.c
  *
  * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -14,31 +14,31 @@
  */
 #include "postgres.h"
 
-#include "commands/sequence.h"
+#include "access/localam.h"
 
 
 void
-seq_desc(StringInfo buf, XLogReaderState *record)
+local_seq_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_local_seq_rec *xlrec = (xl_local_seq_rec *) rec;
 
-	if (info == XLOG_SEQ_LOG)
+	if (info == XLOG_LOCAL_SEQ_LOG)
 		appendStringInfo(buf, "rel %u/%u/%u",
 						 xlrec->locator.spcOid, xlrec->locator.dbOid,
 						 xlrec->locator.relNumber);
 }
 
 const char *
-seq_identify(uint8 info)
+local_seq_identify(uint8 info)
 {
 	const char *id = NULL;
 
 	switch (info & ~XLR_INFO_MASK)
 	{
-		case XLOG_SEQ_LOG:
-			id = "LOG";
+		case XLOG_LOCAL_SEQ_LOG:
+			id = "LOCAL_SEQ_LOG";
 			break;
 	}
 
diff --git a/src/backend/access/rmgrdesc/meson.build b/src/backend/access/rmgrdesc/meson.build
index e8b7a65fc7..5f2f28072b 100644
--- a/src/backend/access/rmgrdesc/meson.build
+++ b/src/backend/access/rmgrdesc/meson.build
@@ -11,13 +11,13 @@ rmgr_desc_sources = files(
   'gistdesc.c',
   'hashdesc.c',
   'heapdesc.c',
+  'localseqdesc.c',
   'logicalmsgdesc.c',
   'mxactdesc.c',
   'nbtdesc.c',
   'relmapdesc.c',
   'replorigindesc.c',
   'rmgrdesc_utils.c',
-  'seqdesc.c',
   'smgrdesc.c',
   'spgdesc.c',
   'standbydesc.c',
diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile
index 9f9d31f542..697f89905e 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = local.o sequence.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/sequence/local.c b/src/backend/access/sequence/local.c
new file mode 100644
index 0000000000..ac124095fb
--- /dev/null
+++ b/src/backend/access/sequence/local.c
@@ -0,0 +1,706 @@
+/*-------------------------------------------------------------------------
+ *
+ * local.c
+ *	  Local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/access/sequence/local.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/localam.h"
+#include "access/multixact.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"
+
+
+/*
+ * 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 LOCAL_SEQ_LOG_VALS	32
+
+/*
+ * The "special area" of a local sequence's buffer page looks like this.
+ */
+#define LOCAL_SEQ_MAGIC	  0x1717
+
+typedef struct local_sequence_magic
+{
+	uint32		magic;
+} local_sequence_magic;
+
+/* Format of tuples stored in heap table associated to local sequences */
+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 local 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
+
+
+/*
+ * 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
+
+static Form_pg_sequence_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_sequence_data
+read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
+{
+	Page		page;
+	ItemId		lp;
+	local_sequence_magic *sm;
+	Form_pg_sequence_data seq;
+
+	*buf = ReadBuffer(rel, 0);
+	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(*buf);
+	sm = (local_sequence_magic *) PageGetSpecialPointer(page);
+
+	if (sm->magic != LOCAL_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;
+}
+
+/*
+ * 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;
+	local_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(local_sequence_magic));
+	sm = (local_sequence_magic *) PageGetSpecialPointer(page);
+	sm->magic = LOCAL_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, (Item) 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_local_seq_rec xlrec;
+		XLogRecPtr	recptr;
+
+		XLogBeginInsert();
+		XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
+
+		xlrec.locator = rel->rd_locator;
+
+		XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec));
+		XLogRegisterData((char *) tuple->t_data, tuple->t_len);
+
+		recptr = XLogInsert(RM_LOCAL_SEQ_ID, XLOG_LOCAL_SEQ_LOG);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buf);
+}
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+local_seq_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+local_seq_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_local_seq_rec *xlrec = (xl_local_seq_rec *) XLogRecGetData(record);
+	local_sequence_magic *sm;
+
+	if (info != XLOG_LOCAL_SEQ_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = (Page) BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	PageInit(localpage, BufferGetPageSize(buffer), sizeof(local_sequence_magic));
+	sm = (local_sequence_magic *) PageGetSpecialPointer(localpage);
+	sm->magic = LOCAL_SEQ_MAGIC;
+
+	item = (char *) xlrec + sizeof(xl_local_seq_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_local_seq_rec);
+
+	if (PageAddItem(localpage, (Item) item, itemsz,
+					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "local_seq_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
+
+/*
+ * local_seq_nextval()
+ *
+ * Allocate a new value for a local sequence, based on the sequence
+ * configuration.
+ */
+int64
+local_seq_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_sequence_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_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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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_local_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 = rel->rd_locator;
+
+		XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec));
+		XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len);
+
+		recptr = XLogInsert(RM_LOCAL_SEQ_ID, XLOG_LOCAL_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);
+
+	return result;
+}
+
+/*
+ * local_seq_get_table_am()
+ *
+ * Return the table access method used by this sequence.
+ */
+const char *
+local_seq_get_table_am(void)
+{
+	return "heap";
+}
+
+/*
+ * local_seq_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
+local_seq_init(Relation rel, int64 last_value, bool is_called)
+{
+	Datum		value[SEQ_COL_LASTCOL];
+	bool		null[SEQ_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_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(0);
+				break;
+			case SEQ_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);
+}
+
+/*
+ * local_seq_setval()
+ *
+ * Callback for setval().
+ */
+void
+local_seq_setval(Relation rel, int64 next, bool iscalled)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_sequence_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_local_seq_rec xlrec;
+		XLogRecPtr	recptr;
+		Page		page = BufferGetPage(buf);
+
+		XLogBeginInsert();
+		XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
+
+		xlrec.locator = rel->rd_locator;
+		XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec));
+		XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len);
+
+		recptr = XLogInsert(RM_LOCAL_SEQ_ID, XLOG_LOCAL_SEQ_LOG);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buf);
+}
+
+/*
+ * local_seq_reset()
+ *
+ * Perform a hard reset on the local sequence, rewriting its heap data
+ * entirely.
+ */
+void
+local_seq_reset(Relation rel, int64 startv, bool is_called, bool reset_state)
+{
+	Form_pg_sequence_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_sequence_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);
+}
+
+/*
+ * local_seq_get_state()
+ *
+ * Retrieve the state of a local sequence.
+ */
+void
+local_seq_get_state(Relation rel, int64 *last_value, bool *is_called)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_sequence_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	*last_value = seq->last_value;
+	*is_called = seq->is_called;
+
+	UnlockReleaseBuffer(buf);
+}
+
+/*
+ * local_seq_change_persistence()
+ *
+ * Persistence change for the local sequence Relation.
+ */
+void
+local_seq_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/sequence/meson.build b/src/backend/access/sequence/meson.build
index 1843aed38d..8c4ef42467 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,6 @@
 # Copyright (c) 2022-2024, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'local.c',
   'sequence.c',
 )
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 3e2f1d4a23..1ec20459cb 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/localam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index d6f2fc3ce0..d7a6523292 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/localam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -50,23 +51,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -96,13 +80,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,
@@ -134,14 +114,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
@@ -174,35 +148,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;
@@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	tuple = heap_form_tuple(tupDesc, value, null);
-	fill_seq_with_data(rel, tuple);
+	/* now initialize the sequence table structure and its data */
+	local_seq_init(rel, last_value, is_called);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -292,10 +222,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;
@@ -306,7 +232,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))
@@ -315,40 +240,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. */
+	local_seq_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 */
@@ -357,106 +250,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, (Item) 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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) tuple->t_data, tuple->t_len);
-
-		recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	UnlockReleaseBuffer(buf);
-}
-
 /*
  * AlterSequence
  *
@@ -468,10 +261,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;
@@ -480,7 +270,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	bool		reset_state = false;
 	bool		is_called;
 	int64		last_value;
-	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
 	relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -507,16 +296,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 */
+	local_seq_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -526,32 +307,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);
+		local_seq_reset(seqrel, last_value, is_called, reset_state);
 	}
 
 	/* Clear local cache so that we don't think we have cached numbers */
@@ -580,8 +339,6 @@ SequenceChangePersistence(Oid relid, char newrelpersistence)
 {
 	SeqTable	elm;
 	Relation	seqrel;
-	Buffer		buf;
-	HeapTupleData seqdatatuple;
 
 	init_sequence(relid, &elm, &seqrel);
 
@@ -589,10 +346,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);
+	local_seq_change_persistence(seqrel, newrelpersistence);
 
 	sequence_close(seqrel, NoLock);
 }
@@ -655,24 +409,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);
@@ -717,105 +462,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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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 = local_seq_nextval(seqrel, incby, maxv, minv, cache, cycle,
+							   &last);
 
 	/* save info in local cache */
 	elm->increment = incby;
@@ -825,69 +474,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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) 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;
@@ -977,9 +563,6 @@ do_setval(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,
@@ -1013,9 +596,6 @@ do_setval(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),
@@ -1037,37 +617,8 @@ do_setval(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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) 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 */
+	local_seq_setval(seqrel, next, iscalled);
 
 	sequence_close(seqrel, NoLock);
 }
@@ -1208,62 +759,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
@@ -1823,9 +1318,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	SeqTable	elm;
 	Relation	seqrel;
 	TupleDesc	tupdesc;
-	Buffer		buf;
-	HeapTupleData seqtuple;
-	Form_pg_sequence_data seq;
 	bool		is_called;
 	int64		last_value;
 
@@ -1842,12 +1334,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 				 errmsg("permission denied for sequence %s",
 						RelationGetRelationName(seqrel))));
 
-	seq = read_seq_tuple(seqrel, &buf, &seqtuple);
-
-	is_called = seq->is_called;
-	last_value = seq->last_value;
-
-	UnlockReleaseBuffer(buf);
+	local_seq_get_state(seqrel, &last_value, &is_called);
 	sequence_close(seqrel, NoLock);
 
 	values[0] = BoolGetDatum(is_called);
@@ -1855,57 +1342,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
 }
 
-
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = (Page) BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, (Item) item, itemsz,
-					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1920,14 +1356,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c76..0f45509f2c 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/localseqdesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index 6b8c17bb4c..ff09335607 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/localam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
-- 
2.43.0

v4-0005-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From 4363bf08870e9e1e53759ecdc1217bc31a701465 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Apr 2024 15:26:29 +0900
Subject: [PATCH v4 5/8] Sequence access methods - backend support

The "local" sequence AM is now plugged in as a handler in the relcache,
and a set of callbacks in sequenceam.h.
---
 src/include/access/localam.h                  |  15 --
 src/include/access/sequenceam.h               | 188 ++++++++++++++++++
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/local.c           |  69 ++++---
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/sequence.c        |   3 +-
 src/backend/access/sequence/sequenceamapi.c   | 145 ++++++++++++++
 src/backend/catalog/heap.c                    |   6 +-
 src/backend/commands/amcmds.c                 |  16 ++
 src/backend/commands/sequence.c               |  21 +-
 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_tables.c           |  12 ++
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 src/bin/psql/describe.c                       |   2 +
 src/bin/psql/tab-complete.c                   |   4 +-
 src/test/regress/expected/create_am.out       |  33 ++-
 src/test/regress/expected/opr_sanity.out      |  12 ++
 src/test/regress/expected/psql.out            |  64 +++---
 src/test/regress/sql/create_am.sql            |  24 ++-
 src/test/regress/sql/opr_sanity.sql           |  10 +
 src/tools/pgindent/typedefs.list              |   5 +-
 39 files changed, 677 insertions(+), 157 deletions(-)
 create mode 100644 src/include/access/sequenceam.h
 create mode 100644 src/backend/access/sequence/sequenceamapi.c

diff --git a/src/include/access/localam.h b/src/include/access/localam.h
index 5b0575dc2e..7afc0a9636 100644
--- a/src/include/access/localam.h
+++ b/src/include/access/localam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_LOCAL_SEQ_LOG			0x00
@@ -31,18 +30,4 @@ extern void local_seq_desc(StringInfo buf, XLogReaderState *record);
 extern const char *local_seq_identify(uint8 info);
 extern void local_seq_mask(char *page, BlockNumber blkno);
 
-/* access routines */
-extern int64 local_seq_nextval(Relation rel, int64 incby, int64 maxv,
-							   int64 minv, int64 cache, bool cycle,
-							   int64 *last);
-extern const char *local_seq_get_table_am(void);
-extern void local_seq_init(Relation rel, int64 last_value, bool is_called);
-extern void local_seq_setval(Relation rel, int64 next, bool iscalled);
-extern void local_seq_reset(Relation rel, int64 startv, bool is_called,
-							bool reset_state);
-extern void local_seq_get_state(Relation rel, int64 *last_value,
-								bool *is_called);
-extern void local_seq_change_persistence(Relation rel,
-										 char newrelpersistence);
-
 #endif							/* LOCALAM_H */
diff --git a/src/include/access/sequenceam.h b/src/include/access/sequenceam.h
new file mode 100644
index 0000000000..f0f36c6c7e
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,188 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2023, 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 "local"
+
+/* 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.
+	 */
+	void		(*get_state) (Relation rel, int64 *last_value, bool *is_called);
+
+	/*
+	 * 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)
+{
+	rel->rd_sequenceam->get_state(rel, last_value, is_called);
+}
+
+/*
+ * 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);
+
+/* ----------------------------------------------------------------------------
+ * Functions in local.c
+ * ----------------------------------------------------------------------------
+ */
+
+extern const SequenceAmRoutine *GetLocalSequenceAmRoutine(void);
+
+#endif							/* SEQUENCEAM_H */
diff --git a/src/include/catalog/pg_am.dat b/src/include/catalog/pg_am.dat
index db87490282..3b31f9df59 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 => '8048', oid_symbol => 'LOCAL_SEQUENCE_AM_OID',
+  descr => 'local sequence access method',
+  amname => 'local', amhandler => '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 475593fad4..3a94e8c636 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 0fc2c093b0..21623f34fa 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -228,6 +228,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)
+
 extern int	errdetail_relkind_not_supported(char relkind);
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 1a8c8ebba5..252323fb78 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 => 'local_sequenceam_handler', provolatile => 'v',
+  prorettype => 'sequence_am_handler', proargtypes => 'internal',
+  prosrc => 'local_sequenceam_handler' },
+
 # Index access method handlers
 { oid => '330', descr => 'btree index access method handler',
   proname => 'bthandler', provolatile => 'v', prorettype => 'index_am_handler',
@@ -7627,6 +7633,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 d29194da31..8f00b322ec 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -626,6 +626,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 5fd095ea17..a43a6cbf85 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -145,6 +145,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 e88cbee3b5..9b22b2f248 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index b665e55b65..774ae2652c 100644
--- a/src/include/nodes/meson.build
+++ b/src/include/nodes/meson.build
@@ -9,6 +9,7 @@ node_support_input_i = [
   '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/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index cfe73cd737..47f0143746 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3133,6 +3133,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 d64dc5fcdb..539ca97003 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -55,6 +55,8 @@ extern bool check_debug_io_direct(char **newval, void **extra, GucSource source)
 extern void assign_debug_io_direct(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 8700204953..2a4b7bed2b 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 697f89905e..b89a7e0526 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = local.o sequence.o
+OBJS = local.o sequence.o sequenceamapi.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/sequence/local.c b/src/backend/access/sequence/local.c
index ac124095fb..efc655f9f5 100644
--- a/src/backend/access/sequence/local.c
+++ b/src/backend/access/sequence/local.c
@@ -18,6 +18,7 @@
 #include "access/bufmask.h"
 #include "access/localam.h"
 #include "access/multixact.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -25,6 +26,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -297,15 +299,15 @@ local_seq_redo(XLogReaderState *record)
 }
 
 /*
- * local_seq_nextval()
+ * local_nextval()
  *
  * Allocate a new value for a local sequence, based on the sequence
  * configuration.
  */
-int64
-local_seq_nextval(Relation rel, int64 incby, int64 maxv,
-				  int64 minv, int64 cache, bool cycle,
-				  int64 *last)
+static int64
+local_nextval(Relation rel, int64 incby, int64 maxv,
+			  int64 minv, int64 cache, bool cycle,
+			  int64 *last)
 {
 	int64		result;
 	int64		fetch;
@@ -485,18 +487,18 @@ local_seq_nextval(Relation rel, int64 incby, int64 maxv,
 }
 
 /*
- * local_seq_get_table_am()
+ * local_get_table_am()
  *
  * Return the table access method used by this sequence.
  */
-const char *
-local_seq_get_table_am(void)
+static const char *
+local_get_table_am(void)
 {
 	return "heap";
 }
 
 /*
- * local_seq_init()
+ * 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
@@ -504,8 +506,8 @@ local_seq_get_table_am(void)
  * inserted after the relation has been created, filling in its heap
  * table.
  */
-void
-local_seq_init(Relation rel, int64 last_value, bool is_called)
+static void
+local_init(Relation rel, int64 last_value, bool is_called)
 {
 	Datum		value[SEQ_COL_LASTCOL];
 	bool		null[SEQ_COL_LASTCOL];
@@ -567,12 +569,12 @@ local_seq_init(Relation rel, int64 last_value, bool is_called)
 }
 
 /*
- * local_seq_setval()
+ * local_setval()
  *
  * Callback for setval().
  */
-void
-local_seq_setval(Relation rel, int64 next, bool iscalled)
+static void
+local_setval(Relation rel, int64 next, bool iscalled)
 {
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
@@ -614,13 +616,13 @@ local_seq_setval(Relation rel, int64 next, bool iscalled)
 }
 
 /*
- * local_seq_reset()
+ * local_reset()
  *
  * Perform a hard reset on the local sequence, rewriting its heap data
  * entirely.
  */
-void
-local_seq_reset(Relation rel, int64 startv, bool is_called, bool reset_state)
+static void
+local_reset(Relation rel, int64 startv, bool is_called, bool reset_state)
 {
 	Form_pg_sequence_data seq;
 	Buffer		buf;
@@ -668,12 +670,12 @@ local_seq_reset(Relation rel, int64 startv, bool is_called, bool reset_state)
 }
 
 /*
- * local_seq_get_state()
+ * local_get_state()
  *
  * Retrieve the state of a local sequence.
  */
-void
-local_seq_get_state(Relation rel, int64 *last_value, bool *is_called)
+static void
+local_get_state(Relation rel, int64 *last_value, bool *is_called)
 {
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
@@ -689,12 +691,12 @@ local_seq_get_state(Relation rel, int64 *last_value, bool *is_called)
 }
 
 /*
- * local_seq_change_persistence()
+ * local_change_persistence()
  *
  * Persistence change for the local sequence Relation.
  */
-void
-local_seq_change_persistence(Relation rel, char newrelpersistence)
+static void
+local_change_persistence(Relation rel, char newrelpersistence)
 {
 	Buffer		buf;
 	HeapTupleData seqdatatuple;
@@ -704,3 +706,24 @@ local_seq_change_persistence(Relation rel, char newrelpersistence)
 	fill_seq_with_data(rel, &seqdatatuple);
 	UnlockReleaseBuffer(buf);
 }
+
+/* ------------------------------------------------------------------------
+ * Definition of the local sequence access method.
+ * ------------------------------------------------------------------------
+ */
+static const SequenceAmRoutine local_methods = {
+	.type = T_SequenceAmRoutine,
+	.get_table_am = local_get_table_am,
+	.init = local_init,
+	.nextval = local_nextval,
+	.setval = local_setval,
+	.reset = local_reset,
+	.get_state = local_get_state,
+	.change_persistence = local_change_persistence
+};
+
+Datum
+local_sequenceam_handler(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_POINTER(&local_methods);
+}
diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build
index 8c4ef42467..9baab19512 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -3,4 +3,5 @@
 backend_sources += files(
   'local.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/sequence.c b/src/backend/access/sequence/sequence.c
index 8d6b7bb5dc..5e7d76d3b0 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 0000000000..4314b9ecb5
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2023, 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/xact.h"
+#include "access/sequenceam.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 f0278b9c01..bca2259e4b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1464,8 +1464,12 @@ 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.
+		 *
+		 * 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)
+		if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE) ||
+			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 aaa0f9a1dc..1a27f191a3 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 d7a6523292..57db43501e 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/localam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -152,6 +152,7 @@ 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;
@@ -173,7 +174,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	rel = sequence_open(seqoid, AccessExclusiveLock);
 
 	/* now initialize the sequence table structure and its data */
-	local_seq_init(rel, last_value, is_called);
+	sequence_init(rel, last_value, is_called);
 
 	/* process OWNED BY if given */
 	if (owned_by)
@@ -241,7 +242,7 @@ ResetSequence(Oid seq_relid)
 	ReleaseSysCache(pgstuple);
 
 	/* Sequence state is forcibly reset here. */
-	local_seq_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 */
@@ -297,7 +298,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
 
 	/* Read sequence data */
-	local_seq_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -310,7 +311,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		if (RelationNeedsWAL(seqrel))
 			GetTopTransactionId();
 
-		local_seq_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 */
@@ -346,7 +347,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence)
 	if (RelationNeedsWAL(seqrel))
 		GetTopTransactionId();
 
-	local_seq_change_persistence(seqrel, newrelpersistence);
+	sequence_change_persistence(seqrel, newrelpersistence);
 
 	sequence_close(seqrel, NoLock);
 }
@@ -463,8 +464,8 @@ nextval_internal(Oid relid, bool check_permissions)
 	ReleaseSysCache(pgstuple);
 
 	/* retrieve next value from the access method */
-	result = local_seq_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;
@@ -618,7 +619,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
 		GetTopTransactionId();
 
 	/* Call the access method callback */
-	local_seq_setval(seqrel, next, iscalled);
+	sequence_setval(seqrel, next, iscalled);
 
 	sequence_close(seqrel, NoLock);
 }
@@ -1334,7 +1335,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 				 errmsg("permission denied for sequence %s",
 						RelationGetRelationName(seqrel))));
 
-	local_seq_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 	sequence_close(seqrel, NoLock);
 
 	values[0] = BoolGetDatum(is_called);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ea63412431..1ba58cec7d 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"
@@ -963,14 +964,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)
 	{
@@ -983,6 +988,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 constraints are passed in
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 66bbad8e6e..86b1773c0b 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -47,6 +47,7 @@ node_headers = \
 	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/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 81df3bdf95..d2450e559d 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -59,6 +59,7 @@ my @all_input_files = qw(
   nodes/execnodes.h
   access/amapi.h
   access/sdir.h
+  access/sequenceam.h
   access/tableam.h
   access/tsmapi.h
   commands/event_trigger.h
@@ -83,6 +84,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 e8b619926e..2e33fb4578 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -391,6 +391,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
@@ -4929,23 +4930,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;
@@ -4982,6 +4986,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); }
 		;
@@ -5978,6 +5987,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 fef084f5d5..ae63168634 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"
@@ -473,6 +474,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence->relpersistence = cxt->rel ? cxt->rel->rd_rel->relpersistence : cxt->relation->relpersistence;
 
 	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 e189e9b79d..e31dbb0ebb 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 262c9878dd..d9e40c5979 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"
@@ -300,6 +302,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);
@@ -1205,8 +1208,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)
 	{
@@ -1215,6 +1217,8 @@ retry:
 		 * inherit.
 		 */
 	}
+	else if (RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind))
+		RelationInitSequenceAccessMethod(relation);
 	else
 		Assert(relation->rd_rel->relam == InvalidOid);
 
@@ -1811,17 +1815,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.
@@ -1852,6 +1848,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
  *
@@ -3670,14 +3709,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);
 
 	/*
 	 * Okay to insert into the relcache hash table.
@@ -4292,13 +4334,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);
@@ -6319,8 +6369,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);
@@ -6332,6 +6384,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_tables.c b/src/backend/utils/misc/guc_tables.c
index c68fdc008b..c384bc00ba 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -30,6 +30,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"
@@ -4139,6 +4140,17 @@ struct config_string ConfigureNamesString[] =
 		check_default_table_access_method, NULL, NULL
 	},
 
+	{
+		{"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets the default sequence access method for new sequences."),
+			NULL,
+			GUC_IS_NAME
+		},
+		&default_sequence_access_method,
+		DEFAULT_SEQUENCE_ACCESS_METHOD,
+		check_default_sequence_access_method, NULL, NULL
+	},
+
 	{
 		{"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default tablespace to create tables and indexes in."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 2166ea4a87..30d1662ce2 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -703,6 +703,7 @@
 					#   error
 #search_path = '"$user", public'	# schema names
 #row_security = on
+#default_sequence_access_method = 'local'
 #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/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index 082cb8a589..1dd822fe44 100644
--- a/src/bin/pg_waldump/t/001_basic.pl
+++ b/src/bin/pg_waldump/t/001_basic.pl
@@ -41,7 +41,7 @@ Btree
 Hash
 Gin
 Gist
-Sequence
+LocalSequence
 SPGist
 BRIN
 CommitTs
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 4a9ee4a54d..bad66978bb 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -161,10 +161,12 @@ describeAccessMethods(const char *pattern, bool verbose)
 					  "SELECT amname AS \"%s\",\n"
 					  "  CASE amtype"
 					  " WHEN 'i' THEN '%s'"
+					  " WHEN 's' THEN '%s'"
 					  " WHEN 't' 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.c b/src/bin/psql/tab-complete.c
index 6fee3160f0..229dc203e3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2200,7 +2200,7 @@ psql_completion(const char *text, int start, int end)
 	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");
@@ -3226,7 +3226,7 @@ psql_completion(const char *text, int start, int end)
 	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 9762c332ce..1e7ce970a8 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;
@@ -500,9 +495,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 = 'local';
 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;
@@ -525,7 +523,7 @@ ORDER BY 3, 1, 2;
  r       | heap2  | tableam_parted_1_heapx
  r       | heap   | tableam_parted_2_heapx
  p       |        | tableam_parted_heapx
- S       |        | tableam_seq_heapx
+ S       | local  | tableam_seq_heapx
  r       | heap2  | tableam_tbl_heapx
  r       | heap2  | tableam_tblas_heapx
  m       | heap2  | tableam_tblmv_heapx
@@ -560,3 +558,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 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 9d047b21b8..ffb4c66853 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1931,6 +1931,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 3bbe4c5f97..52c6a360b6 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4962,8 +4962,8 @@ Indexes:
 -- check printing info about access methods
 \dA
 List of access methods
-  Name  | Type  
---------+-------
+  Name  |   Type   
+--------+----------
  brin   | Index
  btree  | Index
  gin    | Index
@@ -4971,13 +4971,14 @@ List of access methods
  hash   | Index
  heap   | Table
  heap2  | Table
+ local  | Sequence
  spgist | Index
-(8 rows)
+(9 rows)
 
 \dA *
 List of access methods
-  Name  | Type  
---------+-------
+  Name  |   Type   
+--------+----------
  brin   | Index
  btree  | Index
  gin    | Index
@@ -4985,8 +4986,9 @@ List of access methods
  hash   | Index
  heap   | Table
  heap2  | Table
+ local  | Sequence
  spgist | Index
-(8 rows)
+(9 rows)
 
 \dA h*
 List of access methods
@@ -5011,32 +5013,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     | 
+ local  | Sequence | 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     | 
+ local  | Sequence | 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/sql/create_am.sql b/src/test/regress/sql/create_am.sql
index 825aed325e..f928c3349c 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;
@@ -317,9 +314,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 = 'local';
 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;
@@ -355,3 +356,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 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 2fe7b6dcc4..1409622374 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1229,6 +1229,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/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index d551ada325..f2364d6582 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2557,6 +2557,7 @@ SeqScan
 SeqScanState
 SeqTable
 SeqTableData
+SequenceAmRoutine
 SerCommitSeqNo
 SerialControl
 SerialIOData
@@ -3540,6 +3541,7 @@ lineno_t
 list_sort_comparator
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 locale_t
 locate_agg_of_level_context
@@ -3804,7 +3806,6 @@ save_buffer
 scram_state
 scram_state_enum
 sem_t
-sequence_magic
 set_join_pathlist_hook_type
 set_rel_pathlist_hook_type
 shm_mq
@@ -4019,6 +4020,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
@@ -4030,7 +4032,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.43.0

v4-0006-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From efdcdf9b0f9ee85db6de6146b55884db4128945f Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:56:26 +0900
Subject: [PATCH v4 6/8] 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            | 39 ++++++++++++++--
 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     | 63 ++++++++++++++++++++++----
 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, 210 insertions(+), 14 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index fbf5f1c515..4eae73c16e 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -95,6 +95,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
@@ -185,6 +186,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 c7a6c918a6..fbc7e49f36 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -171,6 +171,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;
@@ -1221,6 +1222,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->owner = opts->owner ? pg_strdup(opts->owner) : NULL;
 	newToc->desc = pg_strdup(opts->description);
@@ -2372,6 +2374,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 */
 
@@ -2601,6 +2604,7 @@ WriteToc(ArchiveHandle *AH)
 		WriteStr(AH, te->copyStmt);
 		WriteStr(AH, te->namespace);
 		WriteStr(AH, te->tablespace);
+		WriteStr(AH, te->sequenceam);
 		WriteStr(AH, te->tableam);
 		WriteStr(AH, te->owner);
 		WriteStr(AH, "false");
@@ -2704,6 +2708,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);
 
@@ -3356,6 +3363,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;
 
@@ -3518,6 +3528,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.
  */
@@ -3677,6 +3738,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
 	_becomeOwner(AH, te);
 	_selectOutputSchema(AH, te->namespace);
 	_selectTablespace(AH, te->tablespace);
+	_selectSequenceAccessMethod(AH, te->sequenceam);
 	_selectTableAccessMethod(AH, te->tableam);
 
 	/* Emit header comment for item */
@@ -4171,6 +4233,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);
@@ -4910,6 +4974,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -4969,6 +5034,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 d6104a7196..9eb6f45389 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -70,10 +70,11 @@
 													 * in header */
 #define K_VERS_1_16 MAKE_ARCHIVE_VERSION(1, 16, 0)	/* BLOB METADATA entries
 													 * and multiple BLOBS */
+#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)
 
@@ -321,6 +322,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 */
 
@@ -352,6 +354,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	   *owner;
 	char	   *desc;
@@ -392,6 +395,7 @@ typedef struct _archiveOpts
 	const char *tag;
 	const char *namespace;
 	const char *tablespace;
+	const char *sequenceam;
 	const char *tableam;
 	const char *owner;
 	const char *description;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 851d228f79..83d0cb2165 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -422,6 +422,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},
@@ -1036,6 +1037,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;
@@ -1152,6 +1154,7 @@ help(const char *progname)
 	printf(_("  --no-publications            do not dump publications\n"));
 	printf(_("  --no-security-labels         do not dump security label assignments\n"));
 	printf(_("  --no-subscriptions           do not dump subscriptions\n"));
+	printf(_("  --no-sequence-access-method  do not 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"));
@@ -13306,6 +13309,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;
@@ -17547,7 +17553,8 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 			   *maxv,
 			   *minv,
 			   *cache,
-			   *seqtype;
+			   *seqtype,
+			   *seqam;
 	bool		cycled;
 	bool		is_ascending;
 	int64		default_minv,
@@ -17561,13 +17568,35 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
-	if (fout->remoteVersion >= 100000)
+	if (fout->remoteVersion >= 170000)
 	{
+		/*
+		 * PostgreSQL 17 has added support for sequence access methods.
+		 */
+		appendPQExpBuffer(query,
+						  "SELECT format_type(s.seqtypid, NULL), "
+						  "s.seqstart, s.seqincrement, "
+						  "s.seqmax, s.seqmin, "
+						  "s.seqcache, s.seqcycle, "
+						  "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) "
+						  "WHERE s.seqrelid = '%u'::oid",
+						  tbinfo->dobj.catId.oid);
+	}
+	else if (fout->remoteVersion >= 100000)
+	{
+		/*
+		 * PostgreSQL 10 has moved sequence metadata to the catalog
+		 * pg_sequence.
+		 */
 		appendPQExpBuffer(query,
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
+						  "seqcache, seqcycle, "
+						  "'local' AS seqam "
 						  "FROM pg_catalog.pg_sequence "
 						  "WHERE seqrelid = '%u'::oid",
 						  tbinfo->dobj.catId.oid);
@@ -17583,7 +17612,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 		appendPQExpBuffer(query,
 						  "SELECT 'bigint' AS sequence_type, "
 						  "start_value, increment_by, max_value, min_value, "
-						  "cache_value, is_cycled FROM %s",
+						  "cache_value, is_cycled, 'local' as seqam FROM %s",
 						  fmtQualifiedDumpable(tbinfo));
 	}
 
@@ -17602,6 +17631,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	minv = PQgetvalue(res, 0, 4);
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
+	seqam = PQgetvalue(res, 0, 7);
 
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
@@ -17733,6 +17763,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 					 ARCHIVE_OPTS(.tag = tbinfo->dobj.name,
 								  .namespace = tbinfo->dobj.namespace->dobj.name,
 								  .owner = tbinfo->rolname,
+								  .sequenceam = 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 73337f3392..756a63c24d 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -97,6 +97,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;
@@ -161,6 +162,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},
@@ -435,6 +437,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)
@@ -668,6 +672,7 @@ help(void)
 	printf(_("  --no-security-labels         do not dump security label assignments\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 5ea78cf7cc..077489f4b1 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -68,6 +68,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;
@@ -115,6 +116,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},
@@ -363,6 +365,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;
@@ -491,6 +494,7 @@ usage(const char *progname)
 	printf(_("  --no-publications            do not restore publications\n"));
 	printf(_("  --no-security-labels         do not restore security labels\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(_("  --section=SECTION            restore named section (pre-data, data, or post-data)\n"));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 0c057fef94..690501a189 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -537,6 +537,13 @@ 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', 'postgres',
+		],
+	},
 	no_table_access_method => {
 		dump_cmd => [
 			'pg_dump', '--no-sync',
@@ -711,6 +718,7 @@ my %full_runs = (
 	no_large_objects => 1,
 	no_owner => 1,
 	no_privs => 1,
+	no_sequence_access_method => 1,
 	no_table_access_method => 1,
 	pg_dumpall_dbprivs => 1,
 	pg_dumpall_exclude => 1,
@@ -3855,9 +3863,7 @@ my %tests = (
 		\QCREATE INDEX measurement_city_id_logdate_idx ON ONLY dump_test.measurement USING\E
 		/xm,
 		like => {
-			%full_runs,
-			%dump_test_schema_runs,
-			section_post_data => 1,
+			%full_runs, %dump_test_schema_runs, section_post_data => 1,
 		},
 		unlike => {
 			exclude_dump_test_schema => 1,
@@ -4526,6 +4532,18 @@ my %tests = (
 		},
 	},
 
+	'CREATE ACCESS METHOD regress_test_sequence_am' => {
+		create_order => 11,
+		create_sql =>
+		  'CREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER local_sequenceam_handler;',
+		regexp => qr/^
+			\QCREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER 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
@@ -4554,6 +4572,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 local;
+			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 local;',
+		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 => '
@@ -4783,10 +4830,8 @@ $node->command_fails_like(
 ##############################################################
 # Test dumping pg_catalog (for research -- cannot be reloaded)
 
-$node->command_ok(
-	[ 'pg_dump', '-p', "$port", '-n', 'pg_catalog' ],
-	'pg_dump: option -n pg_catalog'
-);
+$node->command_ok([ 'pg_dump', '-p', "$port", '-n', 'pg_catalog' ],
+	'pg_dump: option -n pg_catalog');
 
 #########################################
 # Test valid database exclusion patterns
@@ -4953,8 +4998,8 @@ foreach my $run (sort keys %pgdump_runs)
 		}
 		# Check for useless entries in "unlike" list.  Runs that are
 		# not listed in "like" don't need to be excluded in "unlike".
-		if ($tests{$test}->{unlike}->{$test_key} &&
-			!defined($tests{$test}->{like}->{$test_key}))
+		if ($tests{$test}->{unlike}->{$test_key}
+			&& !defined($tests{$test}->{like}->{$test_key}))
 		{
 			die "useless \"unlike\" entry \"$test_key\" in test \"$test\"";
 		}
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index b99793e414..bb70d3a40f 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1104,6 +1104,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 4d7c046468..34643175fb 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -479,6 +479,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 2e3ba80258..c552a1e746 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -733,6 +733,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.43.0

v4-0007-dummy_sequence_am-Example-of-sequence-AM.patchtext/x-diff; charset=us-asciiDownload
From b7876b4616463f3d2d18eabd8eb5ab33fbb85681 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:56:52 +0900
Subject: [PATCH v4 7/8] dummy_sequence_am: Example of sequence AM

---
 src/test/modules/Makefile                     |   1 +
 src/test/modules/dummy_sequence_am/.gitignore |   3 +
 src/test/modules/dummy_sequence_am/Makefile   |  19 +++
 .../dummy_sequence_am--1.0.sql                |  13 +++
 .../dummy_sequence_am/dummy_sequence_am.c     | 110 ++++++++++++++++++
 .../dummy_sequence_am.control                 |   5 +
 .../expected/dummy_sequence.out               |  35 ++++++
 .../modules/dummy_sequence_am/meson.build     |  33 ++++++
 .../dummy_sequence_am/sql/dummy_sequence.sql  |  17 +++
 src/test/modules/meson.build                  |   1 +
 10 files changed, 237 insertions(+)
 create mode 100644 src/test/modules/dummy_sequence_am/.gitignore
 create mode 100644 src/test/modules/dummy_sequence_am/Makefile
 create mode 100644 src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql
 create mode 100644 src/test/modules/dummy_sequence_am/dummy_sequence_am.c
 create mode 100644 src/test/modules/dummy_sequence_am/dummy_sequence_am.control
 create mode 100644 src/test/modules/dummy_sequence_am/expected/dummy_sequence.out
 create mode 100644 src/test/modules/dummy_sequence_am/meson.build
 create mode 100644 src/test/modules/dummy_sequence_am/sql/dummy_sequence.sql

diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 256799f520..caeb7a6033 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -10,6 +10,7 @@ SUBDIRS = \
 		  delay_execution \
 		  dummy_index_am \
 		  dummy_seclabel \
+		  dummy_sequence_am \
 		  libpq_pipeline \
 		  plsample \
 		  spgist_name_ops \
diff --git a/src/test/modules/dummy_sequence_am/.gitignore b/src/test/modules/dummy_sequence_am/.gitignore
new file mode 100644
index 0000000000..44d119cfcc
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/.gitignore
@@ -0,0 +1,3 @@
+# Generated subdirectories
+/log/
+/results/
diff --git a/src/test/modules/dummy_sequence_am/Makefile b/src/test/modules/dummy_sequence_am/Makefile
new file mode 100644
index 0000000000..391f7ac946
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/Makefile
@@ -0,0 +1,19 @@
+# src/test/modules/dummy_sequence_am/Makefile
+
+MODULES = dummy_sequence_am
+
+EXTENSION = dummy_sequence_am
+DATA = dummy_sequence_am--1.0.sql
+
+REGRESS = dummy_sequence
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/dummy_sequence_am
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql b/src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql
new file mode 100644
index 0000000000..e12b1f9d87
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql
@@ -0,0 +1,13 @@
+/* src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION dummy_sequence_am" to load this file. \quit
+
+CREATE FUNCTION dummy_sequenceam_handler(internal)
+  RETURNS sequence_am_handler
+  AS 'MODULE_PATHNAME'
+  LANGUAGE C;
+
+CREATE ACCESS METHOD dummy_sequence_am
+  TYPE SEQUENCE HANDLER dummy_sequenceam_handler;
+COMMENT ON ACCESS METHOD dummy_sequence_am IS 'dummy sequence access method';
diff --git a/src/test/modules/dummy_sequence_am/dummy_sequence_am.c b/src/test/modules/dummy_sequence_am/dummy_sequence_am.c
new file mode 100644
index 0000000000..b5ee5d89da
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/dummy_sequence_am.c
@@ -0,0 +1,110 @@
+/*-------------------------------------------------------------------------
+ *
+ * dummy_sequence_am.c
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/test/modules/dummy_sequence_am/dummy_sequence_am.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/sequenceam.h"
+#include "fmgr.h"
+
+PG_MODULE_MAGIC;
+
+/* this sequence is fully on-memory */
+static int	dummy_seqam_last_value = 1;
+static bool dummy_seqam_is_called = false;
+
+PG_FUNCTION_INFO_V1(dummy_sequenceam_handler);
+
+
+/* ------------------------------------------------------------------------
+ * Callbacks for the dummy 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 *
+dummy_sequenceam_get_table_am(void)
+{
+	return "heap";
+}
+
+static void
+dummy_sequenceam_init(Relation rel, int64 last_value, bool is_called)
+{
+	dummy_seqam_last_value = last_value;
+	dummy_seqam_is_called = is_called;
+}
+
+static int64
+dummy_sequenceam_nextval(Relation rel, int64 incby, int64 maxv,
+						 int64 minv, int64 cache, bool cycle,
+						 int64 *last)
+{
+	dummy_seqam_last_value += incby;
+	dummy_seqam_is_called = true;
+
+	return dummy_seqam_last_value;
+}
+
+static void
+dummy_sequenceam_setval(Relation rel, int64 next, bool iscalled)
+{
+	dummy_seqam_last_value = next;
+	dummy_seqam_is_called = iscalled;
+}
+
+static void
+dummy_sequenceam_get_state(Relation rel, int64 *last_value, bool *is_called)
+{
+	*last_value = dummy_seqam_last_value;
+	*is_called = dummy_seqam_is_called;
+}
+
+static void
+dummy_sequenceam_reset(Relation rel, int64 startv, bool is_called,
+					   bool reset_state)
+{
+	dummy_seqam_last_value = startv;
+	dummy_seqam_is_called = is_called;
+}
+
+static void
+dummy_sequenceam_change_persistence(Relation rel, char newrelpersistence)
+{
+	/* nothing to do, really */
+}
+
+/* ------------------------------------------------------------------------
+ * Definition of the dummy sequence access method.
+ * ------------------------------------------------------------------------
+ */
+
+static const SequenceAmRoutine dummy_sequenceam_methods = {
+	.type = T_SequenceAmRoutine,
+	.get_table_am = dummy_sequenceam_get_table_am,
+	.init = dummy_sequenceam_init,
+	.nextval = dummy_sequenceam_nextval,
+	.setval = dummy_sequenceam_setval,
+	.get_state = dummy_sequenceam_get_state,
+	.reset = dummy_sequenceam_reset,
+	.change_persistence = dummy_sequenceam_change_persistence
+};
+
+Datum
+dummy_sequenceam_handler(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_POINTER(&dummy_sequenceam_methods);
+}
diff --git a/src/test/modules/dummy_sequence_am/dummy_sequence_am.control b/src/test/modules/dummy_sequence_am/dummy_sequence_am.control
new file mode 100644
index 0000000000..9f10622f2f
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/dummy_sequence_am.control
@@ -0,0 +1,5 @@
+# dummy_sequence_am extension
+comment = 'dummy_sequence_am - sequence access method template'
+default_version = '1.0'
+module_pathname = '$libdir/dummy_sequence_am'
+relocatable = true
diff --git a/src/test/modules/dummy_sequence_am/expected/dummy_sequence.out b/src/test/modules/dummy_sequence_am/expected/dummy_sequence.out
new file mode 100644
index 0000000000..57588cea5b
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/expected/dummy_sequence.out
@@ -0,0 +1,35 @@
+CREATE EXTENSION dummy_sequence_am;
+CREATE SEQUENCE dummyseq USING dummy_sequence_am;
+SELECT nextval('dummyseq'::regclass);
+ nextval 
+---------
+       2
+(1 row)
+
+SELECT setval('dummyseq'::regclass, 14);
+ setval 
+--------
+     14
+(1 row)
+
+SELECT nextval('dummyseq'::regclass);
+ nextval 
+---------
+      15
+(1 row)
+
+-- Sequence relation exists, but it has no attributes.
+SELECT * FROM dummyseq;
+--
+(0 rows)
+
+-- Reset connection, which will reset the sequence
+\c
+SELECT nextval('dummyseq'::regclass);
+ nextval 
+---------
+       2
+(1 row)
+
+DROP SEQUENCE dummyseq;
+DROP EXTENSION dummy_sequence_am;
diff --git a/src/test/modules/dummy_sequence_am/meson.build b/src/test/modules/dummy_sequence_am/meson.build
new file mode 100644
index 0000000000..84460070e4
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+dummy_sequence_am_sources = files(
+  'dummy_sequence_am.c',
+)
+
+if host_system == 'windows'
+  dummy_sequence_am_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'dummy_sequence_am',
+    '--FILEDESC', 'dummy_sequence_am - sequence access method template',])
+endif
+
+dummy_sequence_am = shared_module('dummy_sequence_am',
+  dummy_sequence_am_sources,
+  kwargs: pg_test_mod_args,
+)
+test_install_libs += dummy_sequence_am
+
+test_install_data += files(
+  'dummy_sequence_am.control',
+  'dummy_sequence_am--1.0.sql',
+)
+
+tests += {
+  'name': 'dummy_sequence_am',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'regress': {
+    'sql': [
+      'dummy_sequence',
+    ],
+  },
+}
diff --git a/src/test/modules/dummy_sequence_am/sql/dummy_sequence.sql b/src/test/modules/dummy_sequence_am/sql/dummy_sequence.sql
new file mode 100644
index 0000000000..c739b29a46
--- /dev/null
+++ b/src/test/modules/dummy_sequence_am/sql/dummy_sequence.sql
@@ -0,0 +1,17 @@
+CREATE EXTENSION dummy_sequence_am;
+
+CREATE SEQUENCE dummyseq USING dummy_sequence_am;
+
+SELECT nextval('dummyseq'::regclass);
+SELECT setval('dummyseq'::regclass, 14);
+SELECT nextval('dummyseq'::regclass);
+
+-- Sequence relation exists, but it has no attributes.
+SELECT * FROM dummyseq;
+
+-- Reset connection, which will reset the sequence
+\c
+SELECT nextval('dummyseq'::regclass);
+
+DROP SEQUENCE dummyseq;
+DROP EXTENSION dummy_sequence_am;
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index d8fe059d23..c977bc36b5 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -5,6 +5,7 @@ subdir('commit_ts')
 subdir('delay_execution')
 subdir('dummy_index_am')
 subdir('dummy_seclabel')
+subdir('dummy_sequence_am')
 subdir('gin')
 subdir('injection_points')
 subdir('ldap_password_func')
-- 
2.43.0

v4-0008-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From fc1f85a968a1f73d1b4dc7ba94b1f021e67bdc06 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v4 8/8] 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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 d8e1282e12..3d595711c3 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8971,6 +8971,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 38ec362d8f..4e1218aa89 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -95,6 +95,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 ec9f90e283..6b82f4c7fc 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -256,6 +256,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 dae43dbaed..3067dc4d4d 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>SEQUENCE</literal> and
+      <literal>INDEX</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 34e9084b5c..d00bdabde0 100644
--- a/doc/src/sgml/ref/create_sequence.sgml
+++ b/doc/src/sgml/ref/create_sequence.sgml
@@ -27,6 +27,7 @@ CREATE [ { TEMPORARY | TEMP } | UNLOGGED ] SEQUENCE [ IF NOT EXISTS ] <replaceab
     [ MINVALUE <replaceable class="parameter">minvalue</replaceable> | NO MINVALUE ] [ MAXVALUE <replaceable class="parameter">maxvalue</replaceable> | NO MAXVALUE ]
     [ START [ WITH ] <replaceable class="parameter">start</replaceable> ] [ CACHE <replaceable class="parameter">cache</replaceable> ] [ [ NO ] CYCLE ]
     [ OWNED BY { <replaceable class="parameter">table_name</replaceable>.<replaceable class="parameter">column_name</replaceable> | NONE } ]
+    [ USING <replaceable class="parameter">access_method</replaceable> ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -261,6 +262,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 0000000000..a96170bfac
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.43.0

#22Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#21)
8 attachment(s)
Re: Sequence Access Methods, round two

On Fri, Apr 19, 2024 at 04:00:28PM +0900, Michael Paquier wrote:

I have plans to rework this patch set for the next commit fest,
and this includes some investigation about custom data types that
could be plugged into these AMs.

So, I have worked more on this patch set, and finished by reorganizing
it more, with more things:
- The in-core sequence access method is split into more files:
-- One for its callbacks, called seqlocalam.c.
-- The WAL replay routines are moved into their own file.
- As asked, Implementation of a contrib module that introduces a
sequence access method for snowflake IDs, to demonstrate what can be
done using the APIs of the patch. The data of such sequences is
stored in an unlogged table, based on the assumption that the
timestamps and the machine IDs ensure the unicity of the IDs for the
sequences. The advantage of what's presented here is that support for
lastval(), nextval() and currval() is straight-forward. Identity
columns are able to feed on that. cache is handled by sequence.c, not
the AM. WAL-logging is needed for the init fork, it goes through the
generic WAL APIs like bloom to log full pages. Some docs are
included. This is still a rough WIP, though, and the buffer handling
is not optimal, and could be made transactional this time (assuming
autovacuum is able to process them at some point, or perhaps the
sequence AMs should offer a way for autovacuum to know if such
sequences should be cleaned up or not).

After having done that, I've also found about a module developed by
pgEdge, that copies a bunch of the code from sequence.c, though it is
not able to handle the sequence cache:
https://github.com/pgEdge/snowflake

The approach this module uses is quite similar to what I have here,
but it is smarter regarding clock ticking, where the internal sequence
counter is bumped only when we fetch the same timestamp as a previous
attempt. The module presented could be switched to do something
similar by storing into the heap table used by the sequence a bit more
data than just the sequence counter. Well, the point I want to make
at this stage is what can be done with sequence AMs, so let's discuss
about that later.

Finally, custom types, where I have come up with a list of open
questions:
- Catalog representation. pg_sequence and pg_sequences switch to
something else than int64.
- The existing functions are also interesting to consider here.
nextval() & co would not be usable as they are for sequence AMs that
use more than int64. Note that the current design taken in the patch
has a strong dependency on int64 (see sequenceam.h). So the types
would need to reflect. With identity columns, the change would not be
that hard as the executor has NextValueExpr. Perhaps each sequence AM
should just have callback equivalents for currval(), nextval() and
lastval(). This hits with the fact that this makes sequence AMs less
transparent to applications because custom data types means different
functions than the native ones.
- Option representation.
- I have polled Twitter and Fosstodon with four choices:
-- No need for that, 64b representation is enough.
-- Possibility to have integer-like types (MSSQL does something like
that).
-- Support for 128b or larger (UUIDs, etc, with text-like
representation or varlenas).
-- Support for binary representation, which comes down to the
possibility of having sequence values even larger than 128b.

Based on the first estimations, 50%-ish of people mentioned than 64b
is more than enough, while Jelte mentioned that Citus has tackled this
problem with an equivalent of 128b (64b for the sequence values, some
more for machine states). Then there's a trend of 25%-ish in favor of
128b and 25%-ish for more than that. The results are far from being
final, but that's something.

My own take after pondering about it is that 64b is still more than
enough for the clustering cases I've seen in the past 15 years or so,
while offering room for implementations even if it comes to thousands
of nodes. So there's some margin depending on the number of bits
reserved for the "machine" part of the sequence IDs when used in
clusters.

The next plan is to hopefully be able to trigger a discussion at the
next pgconf.dev at the end of May, but let's see how it goes.

Thanks,
--
Michael

Attachments:

v5-0001-Switch-pg_sequence_last_value-to-report-a-tuple-a.patchtext/x-diff; charset=us-asciiDownload
From 573886ba8f50ffa59024b3f1a1c31ca4a7d1e006 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 27 Feb 2024 09:02:57 +0900
Subject: [PATCH v5 1/8] Switch pg_sequence_last_value() to report a tuple and
 use it in pg_dump

This commit switches pg_sequence_last_value() to report a tuple made of
(last_value,is_called) that can be directly be used for the arguments of
setval() in a sequence.

Going forward with PostgreSQL 17, pg_dump and pg_sequences make use of
it instead of scanning the heap table assumed to always exist for a
sequence.

Note: this requires a catversion bump.
---
 src/include/catalog/pg_proc.dat      |  6 ++++--
 src/backend/catalog/system_views.sql |  6 +++++-
 src/backend/commands/sequence.c      | 19 +++++++++++++------
 src/bin/pg_dump/pg_dump.c            | 16 +++++++++++++---
 src/test/regress/expected/rules.out  |  7 ++++++-
 5 files changed, 41 insertions(+), 13 deletions(-)

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 134e3b22fd..1a8c8ebba5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3325,9 +3325,11 @@
   proargmodes => '{i,o,o,o,o,o,o,o}',
   proargnames => '{sequence_oid,start_value,minimum_value,maximum_value,increment,cycle_option,cache_size,data_type}',
   prosrc => 'pg_sequence_parameters' },
-{ oid => '4032', descr => 'sequence last value',
+{ oid => '4032', descr => 'sequence last value data',
   proname => 'pg_sequence_last_value', provolatile => 'v', proparallel => 'u',
-  prorettype => 'int8', proargtypes => 'regclass',
+  prorettype => 'record', proargtypes => 'regclass',
+  proallargtypes => '{regclass,bool,int8}', proargmodes => '{i,o,o}',
+  proargnames => '{seqid,is_called,last_value}',
   prosrc => 'pg_sequence_last_value' },
 
 { oid => '275', descr => 'return the next oid for a system table',
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2e61f6d74e..0dfb6346ea 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -178,7 +178,11 @@ CREATE VIEW pg_sequences AS
         S.seqcache AS cache_size,
         CASE
             WHEN has_sequence_privilege(C.oid, 'SELECT,USAGE'::text)
-                THEN pg_sequence_last_value(C.oid)
+                THEN (SELECT
+                          CASE WHEN sl.is_called
+                              THEN sl.last_value ELSE NULL
+                          END
+                      FROM pg_sequence_last_value(C.oid) sl)
             ELSE NULL
         END AS last_value
     FROM pg_sequence S JOIN pg_class C ON (C.oid = S.seqrelid)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 46103561c3..9646c72228 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1774,14 +1774,22 @@ pg_sequence_parameters(PG_FUNCTION_ARGS)
 Datum
 pg_sequence_last_value(PG_FUNCTION_ARGS)
 {
+#define PG_SEQUENCE_LAST_VALUE_COLS		2
 	Oid			relid = PG_GETARG_OID(0);
+	Datum		values[PG_SEQUENCE_LAST_VALUE_COLS] = {0};
+	bool		nulls[PG_SEQUENCE_LAST_VALUE_COLS] = {0};
 	SeqTable	elm;
 	Relation	seqrel;
+	TupleDesc	tupdesc;
 	Buffer		buf;
 	HeapTupleData seqtuple;
 	Form_pg_sequence_data seq;
 	bool		is_called;
-	int64		result;
+	int64		last_value;
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	/* open and lock sequence */
 	init_sequence(relid, &elm, &seqrel);
@@ -1795,15 +1803,14 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	seq = read_seq_tuple(seqrel, &buf, &seqtuple);
 
 	is_called = seq->is_called;
-	result = seq->last_value;
+	last_value = seq->last_value;
 
 	UnlockReleaseBuffer(buf);
 	sequence_close(seqrel, NoLock);
 
-	if (is_called)
-		PG_RETURN_INT64(result);
-	else
-		PG_RETURN_NULL();
+	values[0] = BoolGetDatum(is_called);
+	values[1] = Int64GetDatum(last_value);
+	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
 }
 
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 242ebe807f..54c4f75861 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -17824,9 +17824,19 @@ dumpSequenceData(Archive *fout, const TableDataInfo *tdinfo)
 	bool		called;
 	PQExpBuffer query = createPQExpBuffer();
 
-	appendPQExpBuffer(query,
-					  "SELECT last_value, is_called FROM %s",
-					  fmtQualifiedDumpable(tbinfo));
+	/*
+	 * In versions 17 and up, pg_sequence_last_value() has been switched to
+	 * return a tuple with last_value and is_called.
+	 */
+	if (fout->remoteVersion >= 170000)
+		appendPQExpBuffer(query,
+						  "SELECT last_value, is_called "
+						  "FROM pg_sequence_last_value('%s')",
+						  fmtQualifiedDumpable(tbinfo));
+	else
+		appendPQExpBuffer(query,
+						  "SELECT last_value, is_called FROM %s",
+						  fmtQualifiedDumpable(tbinfo));
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index f4a0f36377..096ac5110b 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1700,7 +1700,12 @@ pg_sequences| SELECT n.nspname AS schemaname,
     s.seqcycle AS cycle,
     s.seqcache AS cache_size,
         CASE
-            WHEN has_sequence_privilege(c.oid, 'SELECT,USAGE'::text) THEN pg_sequence_last_value((c.oid)::regclass)
+            WHEN has_sequence_privilege(c.oid, 'SELECT,USAGE'::text) THEN ( SELECT
+                    CASE
+                        WHEN sl.is_called THEN sl.last_value
+                        ELSE NULL::bigint
+                    END AS "case"
+               FROM pg_sequence_last_value((c.oid)::regclass) sl(is_called, last_value))
             ELSE NULL::bigint
         END AS last_value
    FROM ((pg_sequence s
-- 
2.43.0

v5-0002-Remove-FormData_pg_sequence_data-from-init_params.patchtext/x-diff; charset=us-asciiDownload
From 6ba98cb686a66d38cec4e5dbe42969d469c180fd Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:00:45 +0900
Subject: [PATCH v5 2/8] Remove FormData_pg_sequence_data from
 init_params()/sequence.c

init_params() sets up "last_value" and "is_called" for a sequence, based
on the sequence properties in pg_sequences.  This simplifies the logic
around log_cnt, which is reset to 0 when the metadata of a sequence is
expected to start from afresh when its properties are updated.
---
 src/backend/commands/sequence.c | 81 ++++++++++++++++++++-------------
 1 file changed, 49 insertions(+), 32 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 9646c72228..98a28dfb45 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -106,7 +106,9 @@ static Form_pg_sequence_data read_seq_tuple(Relation rel,
 static void init_params(ParseState *pstate, List *options, bool for_identity,
 						bool isInit,
 						Form_pg_sequence seqform,
-						Form_pg_sequence_data seqdataform,
+						int64 *last_value,
+						bool *reset_state,
+						bool *is_called,
 						bool *need_seq_rewrite,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
@@ -121,7 +123,9 @@ ObjectAddress
 DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 {
 	FormData_pg_sequence seqform;
-	FormData_pg_sequence_data seqdataform;
+	int64		last_value;
+	bool		reset_state;
+	bool		is_called;
 	bool		need_seq_rewrite;
 	List	   *owned_by;
 	CreateStmt *stmt = makeNode(CreateStmt);
@@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* Check and set all option values */
 	init_params(pstate, seq->options, seq->for_identity, true,
-				&seqform, &seqdataform,
+				&seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/*
@@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 		{
 			case SEQ_COL_LASTVAL:
 				coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
-				value[i - 1] = Int64GetDatumFast(seqdataform.last_value);
+				value[i - 1] = Int64GetDatumFast(last_value);
 				break;
 			case SEQ_COL_LOG:
 				coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
@@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	ObjectAddress address;
 	Relation	rel;
 	HeapTuple	seqtuple;
+	bool		reset_state = false;
+	bool		is_called;
+	int64		last_value;
 	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
@@ -481,12 +488,14 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	/* 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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
-				seqform, newdataform,
+				seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/* If needed, rewrite the sequence relation itself */
@@ -513,6 +522,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * 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);
 	}
 
@@ -1229,17 +1242,19 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 /*
  * init_params: process the options list of CREATE or ALTER SEQUENCE, and
  * store the values into appropriate fields of seqform, for changes that go
- * into the pg_sequence catalog, and fields of seqdataform for changes to the
- * sequence relation itself.  Set *need_seq_rewrite to true if we changed any
- * parameters that require rewriting the sequence's relation (interesting for
- * ALTER SEQUENCE).  Also set *owned_by to any OWNED BY option, or to NIL if
- * there is none.
+ * into the pg_sequence catalog, and fields for changes to the sequence
+ * relation itself (is_called, last_value or any state it may hold).  Set
+ * *need_seq_rewrite to true if we changed any parameters that require
+ * rewriting the sequence's relation (interesting for ALTER SEQUENCE).  Also
+ * set *owned_by to any OWNED BY option, or to NIL if there is none.  Set
+ * *reset_state if the internal state of the sequence needs to change on a
+ * follow-up nextval().
  *
  * If isInit is true, fill any unspecified options with default values;
  * otherwise, do not change existing options that aren't explicitly overridden.
  *
  * Note: we force a sequence rewrite whenever we change parameters that affect
- * generation of future sequence values, even if the seqdataform per se is not
+ * generation of future sequence values, even if the metadata per se is not
  * changed.  This allows ALTER SEQUENCE to behave transactionally.  Currently,
  * the only option that doesn't cause that is OWNED BY.  It's *necessary* for
  * ALTER SEQUENCE OWNED BY to not rewrite the sequence, because that would
@@ -1250,7 +1265,9 @@ static void
 init_params(ParseState *pstate, List *options, bool for_identity,
 			bool isInit,
 			Form_pg_sequence seqform,
-			Form_pg_sequence_data seqdataform,
+			int64 *last_value,
+			bool *reset_state,
+			bool *is_called,
 			bool *need_seq_rewrite,
 			List **owned_by)
 {
@@ -1353,11 +1370,11 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	}
 
 	/*
-	 * We must reset log_cnt when isInit or when changing any parameters that
-	 * would affect future nextval allocations.
+	 * We must reset the state when isInit or when changing any parameters
+	 * that would affect future nextval allocations.
 	 */
 	if (isInit)
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 
 	/* AS type */
 	if (as_type != NULL)
@@ -1406,7 +1423,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("INCREMENT must not be zero")));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1418,7 +1435,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	{
 		seqform->seqcycle = boolVal(is_cycled->arg);
 		Assert(BoolIsValid(seqform->seqcycle));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1429,7 +1446,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (max_value != NULL && max_value->arg)
 	{
 		seqform->seqmax = defGetInt64(max_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || max_value != NULL || reset_max_value)
 	{
@@ -1445,7 +1462,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmax = -1;	/* descending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate maximum value.  No need to check INT8 as seqmax is an int64 */
@@ -1461,7 +1478,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (min_value != NULL && min_value->arg)
 	{
 		seqform->seqmin = defGetInt64(min_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || min_value != NULL || reset_min_value)
 	{
@@ -1477,7 +1494,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmin = 1;	/* ascending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate minimum value.  No need to check INT8 as seqmin is an int64 */
@@ -1528,30 +1545,30 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (restart_value != NULL)
 	{
 		if (restart_value->arg != NULL)
-			seqdataform->last_value = defGetInt64(restart_value);
+			*last_value = defGetInt64(restart_value);
 		else
-			seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
-		seqdataform->log_cnt = 0;
+			*last_value = seqform->seqstart;
+		*is_called = false;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-		seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
+		*last_value = seqform->seqstart;
+		*is_called = false;
 	}
 
 	/* crosscheck RESTART (or current value, if changing MIN/MAX) */
-	if (seqdataform->last_value < seqform->seqmin)
+	if (*last_value < seqform->seqmin)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be less than MINVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmin)));
-	if (seqdataform->last_value > seqform->seqmax)
+	if (*last_value > seqform->seqmax)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be greater than MAXVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmax)));
 
 	/* CACHE */
@@ -1563,7 +1580,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("CACHE (%lld) must be greater than zero",
 							(long long) seqform->seqcache)));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-- 
2.43.0

v5-0003-Integrate-addition-of-attributes-for-sequences-wi.patchtext/x-diff; charset=us-asciiDownload
From cf4a668d7da0906eedc634ec9882a6061795f2ef Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v5 3/8] 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               | 29 +++++++++++++++++--
 src/backend/commands/tablecmds.c              | 10 +++++++
 src/backend/tcop/utility.c                    |  4 +++
 .../test_ddl_deparse/expected/alter_table.out | 10 +++++--
 .../expected/create_sequence_1.out            |  5 +++-
 .../expected/create_table.out                 | 15 ++++++++--
 .../test_ddl_deparse/test_ddl_deparse.c       |  3 ++
 8 files changed, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index af80a5d38e..fdcebc268e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2353,6 +2353,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 98a28dfb45..d6f2fc3ce0 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -136,6 +136,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;
@@ -174,7 +177,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;
@@ -198,7 +200,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;
@@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 3556240c8e..ad24777bb8 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4555,6 +4555,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 */
@@ -4858,6 +4859,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,
@@ -5285,6 +5293,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,
@@ -6465,6 +6474,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 fa66b8017e..b64acfb23f 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1664,7 +1664,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 b5e71af9aa..7ebd85200f 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
@@ -71,7 +74,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_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
index 5837ea484e..310ce5a6ba 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.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 75b62aff4d..69e54358ee 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 alter table, tag ALTER TABLE
 NOTICE:    subcommand: type SET ATTNOTNULL desc <NULL>
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 265ef2a547..4be07bcbc8 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -114,6 +114,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.43.0

v5-0004-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From 060ec6d4fa0e8165c72fea39ca5c7b63cc8e4d91 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 25 Apr 2024 16:49:47 +0900
Subject: [PATCH v5 4/8] 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.
- seqlocalxlog.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/seqlocalam.h               |  58 ++
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 .../rmgrdesc/{seqdesc.c => seqlocaldesc.c}    |  20 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/meson.build       |   2 +
 src/backend/access/sequence/seqlocalam.c      | 634 ++++++++++++++++++
 src/backend/access/sequence/seqlocalxlog.c    |  82 +++
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 619 +----------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 14 files changed, 816 insertions(+), 613 deletions(-)
 create mode 100644 src/include/access/seqlocalam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%)
 create mode 100644 src/backend/access/sequence/seqlocalam.c
 create mode 100644 src/backend/access/sequence/seqlocalxlog.c

diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 78e6b908c6..fa94beb6ed 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/seqlocalam.h b/src/include/access/seqlocalam.h
new file mode 100644
index 0000000000..225fb9a2cb
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2024, 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 "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_SEQ_LOCAL_LOG			0x00
+
+typedef struct xl_seq_local_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_seq_local_rec;
+
+/*
+ * 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;
+
+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);
+
+/* 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);
+extern void seq_local_change_persistence(Relation rel,
+										 char newrelpersistence);
+
+#endif							/* SEQLOCALAM_H */
diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile
index cd95eec37f..e5900ed77a 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 e8b7a65fc7..e390d385bf 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 cf0e02ded5..409c67335e 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.c
  *
  * Portions Copyright (c) 1996-2024, 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.h"
+#include "access/seqlocalam.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 9f9d31f542..a15ceec1c0 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 1843aed38d..4d24f900b6 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2024, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'seqlocalam.c',
+  'seqlocalxlog.c',
   'sequence.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
new file mode 100644
index 0000000000..b0a0d72966
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -0,0 +1,634 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.c
+ *	  Local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, 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/multixact.h"
+#include "access/seqlocalam.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"
+
+
+/*
+ * 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
+
+/* 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, (Item) 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((char *) &xlrec, sizeof(xl_seq_local_rec));
+		XLogRegisterData((char *) 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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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((char *) &xlrec, sizeof(xl_seq_local_rec));
+		XLogRegisterData((char *) 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 "heap";
+}
+
+/*
+ * 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((char *) &xlrec, sizeof(xl_seq_local_rec));
+		XLogRegisterData((char *) 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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_seq_local_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	*last_value = seq->last_value;
+	*is_called = seq->is_called;
+
+	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/sequence/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c
new file mode 100644
index 0000000000..db0ad969db
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalxlog.c
@@ -0,0 +1,82 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalxlog.c
+ *	  WAL replay logic for local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/access/sequence/seqlocalxlog.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/seqlocalam.h"
+#include "access/xlogutils.h"
+#include "storage/block.h"
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+seq_local_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+seq_local_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record);
+	seq_local_magic *sm;
+
+	if (info != XLOG_SEQ_LOCAL_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = (Page) BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	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_local_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec);
+
+	if (PageAddItem(localpage, (Item) item, itemsz,
+					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "seq_local_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 3e2f1d4a23..9c22ef315f 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index d6f2fc3ce0..6d712e76e2 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -50,23 +51,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -96,13 +80,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,
@@ -134,14 +114,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
@@ -174,35 +148,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;
@@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	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)
@@ -292,10 +222,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;
@@ -306,7 +232,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))
@@ -315,40 +240,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 */
@@ -357,106 +250,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, (Item) 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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) tuple->t_data, tuple->t_len);
-
-		recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	UnlockReleaseBuffer(buf);
-}
-
 /*
  * AlterSequence
  *
@@ -468,10 +261,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;
@@ -480,7 +270,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	bool		reset_state = false;
 	bool		is_called;
 	int64		last_value;
-	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
 	relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -507,16 +296,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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -526,32 +307,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 */
@@ -580,8 +339,6 @@ SequenceChangePersistence(Oid relid, char newrelpersistence)
 {
 	SeqTable	elm;
 	Relation	seqrel;
-	Buffer		buf;
-	HeapTupleData seqdatatuple;
 
 	init_sequence(relid, &elm, &seqrel);
 
@@ -589,10 +346,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);
 }
@@ -655,24 +409,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);
@@ -717,105 +462,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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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;
@@ -825,69 +474,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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) 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;
@@ -977,9 +563,6 @@ do_setval(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,
@@ -1013,9 +596,6 @@ do_setval(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),
@@ -1037,37 +617,8 @@ do_setval(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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) 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);
 }
@@ -1208,62 +759,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
@@ -1823,9 +1318,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	SeqTable	elm;
 	Relation	seqrel;
 	TupleDesc	tupdesc;
-	Buffer		buf;
-	HeapTupleData seqtuple;
-	Form_pg_sequence_data seq;
 	bool		is_called;
 	int64		last_value;
 
@@ -1842,12 +1334,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 				 errmsg("permission denied for sequence %s",
 						RelationGetRelationName(seqrel))));
 
-	seq = read_seq_tuple(seqrel, &buf, &seqtuple);
-
-	is_called = seq->is_called;
-	last_value = seq->last_value;
-
-	UnlockReleaseBuffer(buf);
+	seq_local_get_state(seqrel, &last_value, &is_called);
 	sequence_close(seqrel, NoLock);
 
 	values[0] = BoolGetDatum(is_called);
@@ -1855,57 +1342,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
 }
 
-
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = (Page) BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, (Item) item, itemsz,
-					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1920,14 +1356,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c76..8d1195de26 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/seqlocaldesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index 6b8c17bb4c..2fccc7a4e2 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index 082cb8a589..8a36f49947 100644
--- a/src/bin/pg_waldump/t/001_basic.pl
+++ b/src/bin/pg_waldump/t/001_basic.pl
@@ -41,7 +41,7 @@ Btree
 Hash
 Gin
 Gist
-Sequence
+SequenceLocal
 SPGist
 BRIN
 CommitTs
-- 
2.43.0

v5-0005-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From 2448144361ef88729a7d56619f90348aca28e7a7 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 25 Apr 2024 17:15:20 +0900
Subject: [PATCH v5 5/8] 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               |  15 --
 src/include/access/sequenceam.h               | 181 ++++++++++++++++++
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/seqlocalam.c      |  41 +++-
 src/backend/access/sequence/sequence.c        |   3 +-
 src/backend/access/sequence/sequenceamapi.c   | 145 ++++++++++++++
 src/backend/catalog/heap.c                    |   6 +-
 src/backend/commands/amcmds.c                 |  16 ++
 src/backend/commands/sequence.c               |  21 +-
 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_tables.c           |  12 ++
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/bin/psql/describe.c                       |   2 +
 src/bin/psql/tab-complete.c                   |   4 +-
 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/sql/create_am.sql            |  24 ++-
 src/test/regress/sql/opr_sanity.sql           |  10 +
 src/tools/pgindent/typedefs.list              |   5 +-
 38 files changed, 682 insertions(+), 169 deletions(-)
 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
index 225fb9a2cb..21936511ac 100644
--- a/src/include/access/seqlocalam.h
+++ b/src/include/access/seqlocalam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_SEQ_LOCAL_LOG			0x00
@@ -41,18 +40,4 @@ 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);
 
-/* 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);
-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 0000000000..ac48c8b468
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,181 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2024, 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.
+	 */
+	void		(*get_state) (Relation rel, int64 *last_value, bool *is_called);
+
+	/*
+	 * 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)
+{
+	rel->rd_sequenceam->get_state(rel, last_value, is_called);
+}
+
+/*
+ * 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 db87490282..d991a77604 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 => '8048', 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 475593fad4..3a94e8c636 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 0fc2c093b0..21623f34fa 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -228,6 +228,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)
+
 extern int	errdetail_relkind_not_supported(char relkind);
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 1a8c8ebba5..f012afbbde 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',
@@ -7627,6 +7633,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 d29194da31..8f00b322ec 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -626,6 +626,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 5fd095ea17..a43a6cbf85 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -145,6 +145,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 e88cbee3b5..9b22b2f248 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index b665e55b65..774ae2652c 100644
--- a/src/include/nodes/meson.build
+++ b/src/include/nodes/meson.build
@@ -9,6 +9,7 @@ node_support_input_i = [
   '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/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index fdcebc268e..5910768cf7 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3133,6 +3133,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 d64dc5fcdb..539ca97003 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -55,6 +55,8 @@ extern bool check_debug_io_direct(char **newval, void **extra, GucSource source)
 extern void assign_debug_io_direct(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 8700204953..2a4b7bed2b 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 a15ceec1c0..62006165a1 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = seqlocalam.o seqlocalxlog.o sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 4d24f900b6..ea91990552 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -4,4 +4,5 @@ backend_sources += files(
   'seqlocalam.c',
   'seqlocalxlog.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
index b0a0d72966..2e524c9e99 100644
--- a/src/backend/access/sequence/seqlocalam.c
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -17,6 +17,7 @@
 
 #include "access/multixact.h"
 #include "access/seqlocalam.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -24,6 +25,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -230,10 +232,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;
@@ -417,7 +419,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 "heap";
@@ -432,7 +434,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];
@@ -499,7 +501,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;
@@ -547,7 +549,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;
@@ -600,7 +602,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)
 {
 	Buffer		buf;
@@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called)
  *
  * Persistence change for the local sequence Relation.
  */
-void
+static void
 seq_local_change_persistence(Relation rel, char newrelpersistence)
 {
 	Buffer		buf;
@@ -632,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 8d6b7bb5dc..5e7d76d3b0 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 0000000000..dd1a60d827
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2024, 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/xact.h"
+#include "access/sequenceam.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 f0278b9c01..bca2259e4b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1464,8 +1464,12 @@ 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.
+		 *
+		 * 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)
+		if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE) ||
+			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 aaa0f9a1dc..1a27f191a3 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 6d712e76e2..57db43501e 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -152,6 +152,7 @@ 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;
@@ -173,7 +174,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)
@@ -241,7 +242,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 */
@@ -297,7 +298,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
 
 	/* Read sequence data */
-	seq_local_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -310,7 +311,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 */
@@ -346,7 +347,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);
 }
@@ -463,8 +464,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;
@@ -618,7 +619,7 @@ do_setval(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);
 }
@@ -1334,7 +1335,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 				 errmsg("permission denied for sequence %s",
 						RelationGetRelationName(seqrel))));
 
-	seq_local_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 	sequence_close(seqrel, NoLock);
 
 	values[0] = BoolGetDatum(is_called);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ad24777bb8..f8a771d1ff 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"
@@ -964,14 +965,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)
 	{
@@ -984,6 +989,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 constraints are passed in
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 66bbad8e6e..86b1773c0b 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -47,6 +47,7 @@ node_headers = \
 	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/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 81df3bdf95..d2450e559d 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -59,6 +59,7 @@ my @all_input_files = qw(
   nodes/execnodes.h
   access/amapi.h
   access/sdir.h
+  access/sequenceam.h
   access/tableam.h
   access/tsmapi.h
   commands/event_trigger.h
@@ -83,6 +84,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 e8b619926e..2e33fb4578 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -391,6 +391,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
@@ -4929,23 +4930,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;
@@ -4982,6 +4986,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); }
 		;
@@ -5978,6 +5987,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 fef084f5d5..ae63168634 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"
@@ -473,6 +474,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence->relpersistence = cxt->rel ? cxt->rel->rd_rel->relpersistence : cxt->relation->relpersistence;
 
 	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 e189e9b79d..e31dbb0ebb 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 262c9878dd..d9e40c5979 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"
@@ -300,6 +302,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);
@@ -1205,8 +1208,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)
 	{
@@ -1215,6 +1217,8 @@ retry:
 		 * inherit.
 		 */
 	}
+	else if (RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind))
+		RelationInitSequenceAccessMethod(relation);
 	else
 		Assert(relation->rd_rel->relam == InvalidOid);
 
@@ -1811,17 +1815,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.
@@ -1852,6 +1848,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
  *
@@ -3670,14 +3709,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);
 
 	/*
 	 * Okay to insert into the relcache hash table.
@@ -4292,13 +4334,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);
@@ -6319,8 +6369,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);
@@ -6332,6 +6384,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_tables.c b/src/backend/utils/misc/guc_tables.c
index c68fdc008b..c384bc00ba 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -30,6 +30,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"
@@ -4139,6 +4140,17 @@ struct config_string ConfigureNamesString[] =
 		check_default_table_access_method, NULL, NULL
 	},
 
+	{
+		{"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets the default sequence access method for new sequences."),
+			NULL,
+			GUC_IS_NAME
+		},
+		&default_sequence_access_method,
+		DEFAULT_SEQUENCE_ACCESS_METHOD,
+		check_default_sequence_access_method, NULL, NULL
+	},
+
 	{
 		{"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default tablespace to create tables and indexes in."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 2166ea4a87..6ee5b5bfca 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -703,6 +703,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 4a9ee4a54d..bad66978bb 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -161,10 +161,12 @@ describeAccessMethods(const char *pattern, bool verbose)
 					  "SELECT amname AS \"%s\",\n"
 					  "  CASE amtype"
 					  " WHEN 'i' THEN '%s'"
+					  " WHEN 's' THEN '%s'"
 					  " WHEN 't' 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.c b/src/bin/psql/tab-complete.c
index 6fee3160f0..229dc203e3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2200,7 +2200,7 @@ psql_completion(const char *text, int start, int end)
 	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");
@@ -3226,7 +3226,7 @@ psql_completion(const char *text, int start, int end)
 	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 9762c332ce..b6566e27d5 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;
@@ -500,9 +495,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;
@@ -519,18 +517,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
@@ -560,3 +558,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 9d047b21b8..ffb4c66853 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1931,6 +1931,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 3bbe4c5f97..e64e50bbb9 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4962,31 +4962,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
@@ -5011,32 +5013,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/sql/create_am.sql b/src/test/regress/sql/create_am.sql
index 825aed325e..472289994e 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;
@@ -317,9 +314,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;
@@ -355,3 +356,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 2fe7b6dcc4..1409622374 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1229,6 +1229,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/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 90a7c389b2..fedf2a77d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2557,6 +2557,7 @@ SeqScan
 SeqScanState
 SeqTable
 SeqTableData
+SequenceAmRoutine
 SerCommitSeqNo
 SerialControl
 SerialIOData
@@ -3541,6 +3542,7 @@ lineno_t
 list_sort_comparator
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 locale_t
 locate_agg_of_level_context
@@ -3805,7 +3807,6 @@ save_buffer
 scram_state
 scram_state_enum
 sem_t
-sequence_magic
 set_join_pathlist_hook_type
 set_rel_pathlist_hook_type
 shm_mq
@@ -4020,6 +4021,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
@@ -4031,7 +4033,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.43.0

v5-0006-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From 0043f858105559de904fd6b4f1efcabcc832c5de Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 25 Apr 2024 17:28:27 +0900
Subject: [PATCH v5 6/8] 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            | 39 ++++++++++++++--
 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     | 63 ++++++++++++++++++++++----
 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, 210 insertions(+), 14 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index fbf5f1c515..4eae73c16e 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -95,6 +95,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
@@ -185,6 +186,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 c6c101c118..f5d2b2fa4f 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -174,6 +174,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;
@@ -1224,6 +1225,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;
@@ -2376,6 +2378,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 */
 
@@ -2605,6 +2608,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);
@@ -2709,6 +2713,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);
 
@@ -3364,6 +3371,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;
 
@@ -3526,6 +3536,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.
  */
@@ -3735,6 +3796,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
 	_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);
 
@@ -4237,6 +4299,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);
@@ -4976,6 +5040,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -5035,6 +5100,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 ce5ed1dd39..cdfebcd340 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)
 
@@ -322,6 +323,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 */
 
@@ -353,6 +355,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;
@@ -394,6 +397,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 54c4f75861..c1c7e40e20 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -422,6 +422,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},
@@ -1036,6 +1037,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;
@@ -1152,6 +1154,7 @@ help(const char *progname)
 	printf(_("  --no-publications            do not dump publications\n"));
 	printf(_("  --no-security-labels         do not dump security label assignments\n"));
 	printf(_("  --no-subscriptions           do not dump subscriptions\n"));
+	printf(_("  --no-sequence-access-method  do not 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"));
@@ -13317,6 +13320,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;
@@ -17559,7 +17565,8 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 			   *maxv,
 			   *minv,
 			   *cache,
-			   *seqtype;
+			   *seqtype,
+			   *seqam;
 	bool		cycled;
 	bool		is_ascending;
 	int64		default_minv,
@@ -17573,13 +17580,35 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
-	if (fout->remoteVersion >= 100000)
+	if (fout->remoteVersion >= 170000)
 	{
+		/*
+		 * PostgreSQL 17 has added support for sequence access methods.
+		 */
+		appendPQExpBuffer(query,
+						  "SELECT format_type(s.seqtypid, NULL), "
+						  "s.seqstart, s.seqincrement, "
+						  "s.seqmax, s.seqmin, "
+						  "s.seqcache, s.seqcycle, "
+						  "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) "
+						  "WHERE s.seqrelid = '%u'::oid",
+						  tbinfo->dobj.catId.oid);
+	}
+	else if (fout->remoteVersion >= 100000)
+	{
+		/*
+		 * PostgreSQL 10 has moved sequence metadata to the catalog
+		 * pg_sequence.
+		 */
 		appendPQExpBuffer(query,
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
+						  "seqcache, seqcycle, "
+						  "'seqlocal' AS seqam "
 						  "FROM pg_catalog.pg_sequence "
 						  "WHERE seqrelid = '%u'::oid",
 						  tbinfo->dobj.catId.oid);
@@ -17595,7 +17624,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 		appendPQExpBuffer(query,
 						  "SELECT 'bigint' AS sequence_type, "
 						  "start_value, increment_by, max_value, min_value, "
-						  "cache_value, is_cycled FROM %s",
+						  "cache_value, is_cycled, 'seqlocal' as seqam FROM %s",
 						  fmtQualifiedDumpable(tbinfo));
 	}
 
@@ -17614,6 +17643,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	minv = PQgetvalue(res, 0, 4);
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
+	seqam = PQgetvalue(res, 0, 7);
 
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
@@ -17745,6 +17775,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 					 ARCHIVE_OPTS(.tag = tbinfo->dobj.name,
 								  .namespace = tbinfo->dobj.namespace->dobj.name,
 								  .owner = tbinfo->rolname,
+								  .sequenceam = 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 73337f3392..756a63c24d 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -97,6 +97,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;
@@ -161,6 +162,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},
@@ -435,6 +437,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)
@@ -668,6 +672,7 @@ help(void)
 	printf(_("  --no-security-labels         do not dump security label assignments\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 5ea78cf7cc..077489f4b1 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -68,6 +68,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;
@@ -115,6 +116,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},
@@ -363,6 +365,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;
@@ -491,6 +494,7 @@ usage(const char *progname)
 	printf(_("  --no-publications            do not restore publications\n"));
 	printf(_("  --no-security-labels         do not restore security labels\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(_("  --section=SECTION            restore named section (pre-data, data, or post-data)\n"));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 7085053a2d..0f5bc6f812 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -537,6 +537,13 @@ 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', 'postgres',
+		],
+	},
 	no_table_access_method => {
 		dump_cmd => [
 			'pg_dump', '--no-sync',
@@ -711,6 +718,7 @@ my %full_runs = (
 	no_large_objects => 1,
 	no_owner => 1,
 	no_privs => 1,
+	no_sequence_access_method => 1,
 	no_table_access_method => 1,
 	pg_dumpall_dbprivs => 1,
 	pg_dumpall_exclude => 1,
@@ -3855,9 +3863,7 @@ my %tests = (
 		\QCREATE INDEX measurement_city_id_logdate_idx ON ONLY dump_test.measurement USING\E
 		/xm,
 		like => {
-			%full_runs,
-			%dump_test_schema_runs,
-			section_post_data => 1,
+			%full_runs, %dump_test_schema_runs, section_post_data => 1,
 		},
 		unlike => {
 			exclude_dump_test_schema => 1,
@@ -4526,6 +4532,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
@@ -4554,6 +4572,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 => '
@@ -4783,10 +4830,8 @@ $node->command_fails_like(
 ##############################################################
 # Test dumping pg_catalog (for research -- cannot be reloaded)
 
-$node->command_ok(
-	[ 'pg_dump', '-p', "$port", '-n', 'pg_catalog' ],
-	'pg_dump: option -n pg_catalog'
-);
+$node->command_ok([ 'pg_dump', '-p', "$port", '-n', 'pg_catalog' ],
+	'pg_dump: option -n pg_catalog');
 
 #########################################
 # Test valid database exclusion patterns
@@ -4953,8 +4998,8 @@ foreach my $run (sort keys %pgdump_runs)
 		}
 		# Check for useless entries in "unlike" list.  Runs that are
 		# not listed in "like" don't need to be excluded in "unlike".
-		if ($tests{$test}->{unlike}->{$test_key} &&
-			!defined($tests{$test}->{like}->{$test_key}))
+		if ($tests{$test}->{unlike}->{$test_key}
+			&& !defined($tests{$test}->{like}->{$test_key}))
 		{
 			die "useless \"unlike\" entry \"$test_key\" in test \"$test\"";
 		}
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 08d775379f..0261a0fcbf 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1104,6 +1104,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 4d7c046468..34643175fb 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -479,6 +479,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 2e3ba80258..c552a1e746 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -733,6 +733,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.43.0

v5-0007-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From 19d13ccc50a165f22b41a3fe381ec67744660c41 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v5 7/8] 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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 d8e1282e12..3d595711c3 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8971,6 +8971,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 38ec362d8f..4e1218aa89 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -95,6 +95,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 ec9f90e283..6b82f4c7fc 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -256,6 +256,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 dae43dbaed..3067dc4d4d 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>SEQUENCE</literal> and
+      <literal>INDEX</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 34e9084b5c..d00bdabde0 100644
--- a/doc/src/sgml/ref/create_sequence.sgml
+++ b/doc/src/sgml/ref/create_sequence.sgml
@@ -27,6 +27,7 @@ CREATE [ { TEMPORARY | TEMP } | UNLOGGED ] SEQUENCE [ IF NOT EXISTS ] <replaceab
     [ MINVALUE <replaceable class="parameter">minvalue</replaceable> | NO MINVALUE ] [ MAXVALUE <replaceable class="parameter">maxvalue</replaceable> | NO MAXVALUE ]
     [ START [ WITH ] <replaceable class="parameter">start</replaceable> ] [ CACHE <replaceable class="parameter">cache</replaceable> ] [ [ NO ] CYCLE ]
     [ OWNED BY { <replaceable class="parameter">table_name</replaceable>.<replaceable class="parameter">column_name</replaceable> | NONE } ]
+    [ USING <replaceable class="parameter">access_method</replaceable> ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -261,6 +262,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 0000000000..a96170bfac
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.43.0

v5-0008-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From 07b626d6641e7bb871caf7f4e35262097bc988d0 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 26 Apr 2024 15:09:00 +0900
Subject: [PATCH v5 8/8] snowflake: Add sequence AM based on it

This includes documentation and a basic implementation, though this is
in a very early stage.
---
 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            | 570 +++++++++++++++++++++++
 contrib/snowflake/snowflake.control      |   5 +
 contrib/snowflake/sql/snowflake.sql      |  29 ++
 13 files changed, 858 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 44639a8dca..e7b8871e8a 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -166,6 +166,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 4e1218aa89..3b9e38e320 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -156,6 +156,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 0000000000..060699e7ec
--- /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 abd780f277..f5d1b568b2 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -42,6 +42,7 @@ SUBDIRS = \
 		pg_walinspect	\
 		postgres_fdw	\
 		seg		\
+		snowflake	\
 		spi		\
 		tablefunc	\
 		tcn		\
diff --git a/contrib/meson.build b/contrib/meson.build
index 14a8906865..4f2603e7e9 100644
--- a/contrib/meson.build
+++ b/contrib/meson.build
@@ -57,6 +57,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 0000000000..44d119cfcc
--- /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 0000000000..fa5b48d565
--- /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 0000000000..b7e469bf73
--- /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 0000000000..567669eea7
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2024, 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 += bloom
+
+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 0000000000..bcb9d754f1
--- /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 0000000000..be4c4039ec
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,570 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2024, 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/sequenceam.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 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(snowflake_magic));
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+	sm->magic = SNOWFLAKE_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 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, (Item) 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)
+{
+	Page		page;
+	ItemId		lp;
+	snowflake_magic *sm;
+	Form_snowflake_data	seq;
+
+	*buf = ReadBuffer(rel, 0);
+	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(*buf);
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+
+	if (sm->magic != SNOWFLAKE_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_snowflake_data) GETSTRUCT(seqdatatuple);
+
+	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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_snowflake_data seq;
+
+	seq = read_snowflake(rel, &buf, &seqdatatuple);
+	*last_value = seq->count;
+	*is_called = seq->is_called;
+	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 0000000000..7b8c6089c2
--- /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 0000000000..395d166ba4
--- /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.43.0

#23Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#22)
8 attachment(s)
Re: Sequence Access Methods, round two

On Fri, Apr 26, 2024 at 03:21:29PM +0900, Michael Paquier wrote:

The next plan is to hopefully be able to trigger a discussion at the
next pgconf.dev at the end of May, but let's see how it goes.

I am a bit behind an update of this thread, but there has been an
unconference on the topic at the last pgconf.dev. This is based on my
own notes written down after the session, so if there are gaps, feel
free to correct me. The session was called "Sequences & Clusters",
and was in two parts, with the first part covering this thread, and
the second part covering the problem of sequences with logical
replication for upgrade cases. I've taken a lot of time with the 1st
part (sorry about that Amit K.!) still the second part has reached an
agreement about what to do next there, and this is covered by this
thread these days:
/messages/by-id/CAA4eK1LC+KJiAkSrpE_NwvNdidw9F2os7GERUeSxSKv71gXysQ@mail.gmail.com

My overall feeling before this session was that I did not feel that
folks grabbed the problem I was trying to solve, and, while it did not
feel that the end of the session completely filled the gaps, and least
folks finished with some idea of the reason why I've been trying
something here.

First, I have spoken for a few minutes about the use-cases I've been
trying to solve, where parts of it involve Postgres-XC, an
auto-proclaimed multi-master solution fork of Postgres, where
sequences are handled by patching src/backend/commands/sequence.c to
retrieve values from a GTM (global transaction manager, source of
truth for value uniqueness shared by all the nodes), something I got
my hands on between 2009~2012 (spoiler: people tend to like more
scaling out clusters 12 years later). Then explained why Postgres is
not good in this area. The original idea is that we want to be able
for some applications to scale out Postgres across many hosts while
making it transparent to the user's applications. By that, imagine a
huge big box where users can connect to a single point, but
underground any connection could involve a connection to a cluster of
N PostgreSQL nodes, N being rather large (say N > 10k?).

Why would we want that? One problem behind such configurations is
that there is no way to make the values transparent for the
application without applying schema changes (attribute defaults, UUIDs
but these are large, for example), meaning that schemas cannot really
be migrated as-they-are from one space (be it a Postgres cluster of 1
or more nodes) to a second space (with less more or more nodes), and
having to manipulate clusters with ALTER SEQUENCE commands to ensure
that there is no overlap in value does not help much to avoid support
at 3AM in case of sudden value conflicts because an application has
gone wild, especially if the node fleet needs to be elastic and
flexible (yep, there's also that). Note that there are also limits
with generated columns that feed from the in-core sequence computation
of Postgres where all the sequence data is stored in a pseudo-heap
table, relying on buffer locks to make sure that in-place updates are
concurrent safe. So this thread is about extending the set of
possibilities in this area for application developers to control how
sequences are computed.

First here is a summary of the use cases that have been mentioned
where a custom computation is handy, based on properties that I've
understood from the conversation:
- Control of computation of values on a node and cluster-basis,
usually coming with three properties (put a snowflake ID here):
-- Global component, usually put in the first bits to force an
ordering of the values across all the nodes. For snowflakes, this is
covered by a timestamp, to which an offset can be applied.
-- Local component, where a portion of the value bits are decided
depending on the node where the value is computed.
-- Local incrementation, where the last bits in the value are used to
loop if the two first ones happen to be equal, to ensure uniqueness.
- Cache range of values at node level or session level, retrieved from
a unique source shared by multiple nodes. The range of values is
retrieved from a single source (PostgreSQL node itself), cached in a
shared pool in a node or just a backend context for consumption by a
session.
- Transactional behavior to minimize value gaps, which is something I
have mentioned but I'm a bit meh on this property as value uniqueness
is key, while users have learnt to live with value gaps. Still the
APIs can make that possible if autovacuum is able to understand that
some clean up needs to happen.

Another set of things that have been mentioned:
- Is it even correct to call this concept an access method? Should a
different keyword be used? This depends on the stack layer where the
callbacks associated to a sequence are added, I assume. Still, based
on the infrastructure that we already have in place for tables and
indexes (commands, GUCs), this is still kind of the correct concept to
me because we can rely on a lot of existing infrastructure, but I also
get that depending on one's view the opinion diverges.
- More pluggable layers. The final picture will most likely involve
multiple layers of APIs, and not only what's proposed here, with
xpoints mentioned about:
-- Custom data types. My answer on this one is that this will need to
be controlled by a different clause. I think that this is a different
feature than the "access method" approach proposed here that would
need to happen on top of what's here, where the point is to control
the computation (and anything I've seen lately would unlock up to 64b
of computation space hidden behind integer-like data types). Other
cluster products out there have also a concept of user-related data
types, which have to be integer-like.
-- Custom nextval() functions. Here we are going to need a split
between the in-core portion of sequences related to system catalogs
and the facilities that can be accessed once a sequence OID is known.
The patch proposed plugs into nextval_internal() for two reasons:
being able to let CACHE be handled by the core code and not the AM,
and easier support for generated columns with the existing types where
nextval_internal() is called from the executor. This part, also,
is going to require a new SQL clause. Perhaps something will happen
at some point in the SQL specification itself to put some guidelines,
who knows.

While on it, I have noticed a couple of conflicts while rebasing, so
attached is a refreshed patch set.

Thanks,
--
Michael

Attachments:

v6-0008-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From b07b25138ea90ac447f6bd36d7c6d4d8dc405a69 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 26 Apr 2024 15:09:00 +0900
Subject: [PATCH v6 8/8] snowflake: Add sequence AM based on it

This includes documentation and a basic implementation, though this is
in a very early stage.
---
 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            | 570 +++++++++++++++++++++++
 contrib/snowflake/snowflake.control      |   5 +
 contrib/snowflake/sql/snowflake.sql      |  29 ++
 13 files changed, 858 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 44639a8dca..e7b8871e8a 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -166,6 +166,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 4e1218aa89..3b9e38e320 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -156,6 +156,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 0000000000..060699e7ec
--- /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 abd780f277..f5d1b568b2 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -42,6 +42,7 @@ SUBDIRS = \
 		pg_walinspect	\
 		postgres_fdw	\
 		seg		\
+		snowflake	\
 		spi		\
 		tablefunc	\
 		tcn		\
diff --git a/contrib/meson.build b/contrib/meson.build
index 14a8906865..4f2603e7e9 100644
--- a/contrib/meson.build
+++ b/contrib/meson.build
@@ -57,6 +57,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 0000000000..44d119cfcc
--- /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 0000000000..fa5b48d565
--- /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 0000000000..b7e469bf73
--- /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 0000000000..567669eea7
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2024, 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 += bloom
+
+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 0000000000..bcb9d754f1
--- /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 0000000000..be4c4039ec
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,570 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2024, 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/sequenceam.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 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(snowflake_magic));
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+	sm->magic = SNOWFLAKE_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 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, (Item) 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)
+{
+	Page		page;
+	ItemId		lp;
+	snowflake_magic *sm;
+	Form_snowflake_data	seq;
+
+	*buf = ReadBuffer(rel, 0);
+	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(*buf);
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+
+	if (sm->magic != SNOWFLAKE_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_snowflake_data) GETSTRUCT(seqdatatuple);
+
+	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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_snowflake_data seq;
+
+	seq = read_snowflake(rel, &buf, &seqdatatuple);
+	*last_value = seq->count;
+	*is_called = seq->is_called;
+	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 0000000000..7b8c6089c2
--- /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 0000000000..395d166ba4
--- /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.45.1

v6-0006-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From 4452ea9622f0eecf439e5b1dc09ac46e3fb8202a Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 25 Apr 2024 17:28:27 +0900
Subject: [PATCH v6 6/8] 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            | 39 ++++++++++++++--
 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     | 49 +++++++++++++++++++++
 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, 205 insertions(+), 5 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index fbf5f1c515..4eae73c16e 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -95,6 +95,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
@@ -185,6 +186,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 68e321212d..a2a8340473 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -174,6 +174,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;
@@ -1224,6 +1225,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;
@@ -2379,6 +2381,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 */
 
@@ -2608,6 +2611,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);
@@ -2712,6 +2716,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);
 
@@ -3367,6 +3374,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;
 
@@ -3529,6 +3539,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.
  */
@@ -3738,6 +3799,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
 	_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);
 
@@ -4240,6 +4302,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);
@@ -4979,6 +5043,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -5038,6 +5103,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 ce5ed1dd39..cdfebcd340 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)
 
@@ -322,6 +323,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 */
 
@@ -353,6 +355,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;
@@ -394,6 +397,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 4b1674c5df..442bffd275 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -422,6 +422,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},
@@ -1036,6 +1037,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;
@@ -1152,6 +1154,7 @@ help(const char *progname)
 	printf(_("  --no-publications            do not dump publications\n"));
 	printf(_("  --no-security-labels         do not dump security label assignments\n"));
 	printf(_("  --no-subscriptions           do not dump subscriptions\n"));
+	printf(_("  --no-sequence-access-method  do not 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"));
@@ -13110,6 +13113,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;
@@ -17297,7 +17303,8 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 			   *maxv,
 			   *minv,
 			   *cache,
-			   *seqtype;
+			   *seqtype,
+			   *seqam;
 	bool		cycled;
 	bool		is_ascending;
 	int64		default_minv,
@@ -17311,13 +17318,35 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 
 	qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
 
-	if (fout->remoteVersion >= 100000)
+	if (fout->remoteVersion >= 170000)
 	{
+		/*
+		 * PostgreSQL 17 has added support for sequence access methods.
+		 */
+		appendPQExpBuffer(query,
+						  "SELECT format_type(s.seqtypid, NULL), "
+						  "s.seqstart, s.seqincrement, "
+						  "s.seqmax, s.seqmin, "
+						  "s.seqcache, s.seqcycle, "
+						  "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) "
+						  "WHERE s.seqrelid = '%u'::oid",
+						  tbinfo->dobj.catId.oid);
+	}
+	else if (fout->remoteVersion >= 100000)
+	{
+		/*
+		 * PostgreSQL 10 has moved sequence metadata to the catalog
+		 * pg_sequence.
+		 */
 		appendPQExpBuffer(query,
 						  "SELECT format_type(seqtypid, NULL), "
 						  "seqstart, seqincrement, "
 						  "seqmax, seqmin, "
-						  "seqcache, seqcycle "
+						  "seqcache, seqcycle, "
+						  "'seqlocal' AS seqam "
 						  "FROM pg_catalog.pg_sequence "
 						  "WHERE seqrelid = '%u'::oid",
 						  tbinfo->dobj.catId.oid);
@@ -17333,7 +17362,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 		appendPQExpBuffer(query,
 						  "SELECT 'bigint' AS sequence_type, "
 						  "start_value, increment_by, max_value, min_value, "
-						  "cache_value, is_cycled FROM %s",
+						  "cache_value, is_cycled, 'seqlocal' as seqam FROM %s",
 						  fmtQualifiedDumpable(tbinfo));
 	}
 
@@ -17352,6 +17381,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 	minv = PQgetvalue(res, 0, 4);
 	cache = PQgetvalue(res, 0, 5);
 	cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
+	seqam = PQgetvalue(res, 0, 7);
 
 	/* Calculate default limits for a sequence of this type */
 	is_ascending = (incby[0] != '-');
@@ -17483,6 +17513,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo)
 					 ARCHIVE_OPTS(.tag = tbinfo->dobj.name,
 								  .namespace = tbinfo->dobj.namespace->dobj.name,
 								  .owner = tbinfo->rolname,
+								  .sequenceam = 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 882dbf8e86..a35bd3a044 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -97,6 +97,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;
@@ -161,6 +162,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},
@@ -435,6 +437,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)
@@ -668,6 +672,7 @@ help(void)
 	printf(_("  --no-security-labels         do not dump security label assignments\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 df119591cc..081954c5ce 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -68,6 +68,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;
@@ -115,6 +116,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},
@@ -363,6 +365,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;
@@ -491,6 +494,7 @@ usage(const char *progname)
 	printf(_("  --no-publications            do not restore publications\n"));
 	printf(_("  --no-security-labels         do not restore security labels\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(_("  --section=SECTION            restore named section (pre-data, data, or post-data)\n"));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index d3dd8784d6..1f437f0ee9 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -537,6 +537,13 @@ 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', 'postgres',
+		],
+	},
 	no_table_access_method => {
 		dump_cmd => [
 			'pg_dump', '--no-sync',
@@ -711,6 +718,7 @@ my %full_runs = (
 	no_large_objects => 1,
 	no_owner => 1,
 	no_privs => 1,
+	no_sequence_access_method => 1,
 	no_table_access_method => 1,
 	pg_dumpall_dbprivs => 1,
 	pg_dumpall_exclude => 1,
@@ -4488,6 +4496,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
@@ -4516,6 +4536,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 b95ed87517..abe78c0d36 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1109,6 +1109,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 4d7c046468..34643175fb 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -479,6 +479,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 2e3ba80258..c552a1e746 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -733,6 +733,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.45.1

v6-0007-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From d1bb7871bc6aac53cc7e6fc38be443cc3ab65bf7 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v6 7/8] 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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 698169afdb..b4bce394e4 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8962,6 +8962,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 38ec362d8f..4e1218aa89 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -95,6 +95,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 ec9f90e283..6b82f4c7fc 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -256,6 +256,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 dae43dbaed..3067dc4d4d 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>SEQUENCE</literal> and
+      <literal>INDEX</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 34e9084b5c..d00bdabde0 100644
--- a/doc/src/sgml/ref/create_sequence.sgml
+++ b/doc/src/sgml/ref/create_sequence.sgml
@@ -27,6 +27,7 @@ CREATE [ { TEMPORARY | TEMP } | UNLOGGED ] SEQUENCE [ IF NOT EXISTS ] <replaceab
     [ MINVALUE <replaceable class="parameter">minvalue</replaceable> | NO MINVALUE ] [ MAXVALUE <replaceable class="parameter">maxvalue</replaceable> | NO MAXVALUE ]
     [ START [ WITH ] <replaceable class="parameter">start</replaceable> ] [ CACHE <replaceable class="parameter">cache</replaceable> ] [ [ NO ] CYCLE ]
     [ OWNED BY { <replaceable class="parameter">table_name</replaceable>.<replaceable class="parameter">column_name</replaceable> | NONE } ]
+    [ USING <replaceable class="parameter">access_method</replaceable> ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -261,6 +262,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 0000000000..a96170bfac
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.45.1

v6-0001-Switch-pg_sequence_last_value-to-report-a-tuple-a.patchtext/x-diff; charset=us-asciiDownload
From 63112231dd41af6898b16d8e52de44b120ed89c7 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 27 Feb 2024 09:02:57 +0900
Subject: [PATCH v6 1/8] Switch pg_sequence_last_value() to report a tuple and
 use it in pg_dump

This commit switches pg_sequence_last_value() to report a tuple made of
(last_value,is_called) that can be directly be used for the arguments of
setval() in a sequence.

Going forward with PostgreSQL 17, pg_dump and pg_sequences make use of
it instead of scanning the heap table assumed to always exist for a
sequence.

Note: this requires a catversion bump.
---
 src/include/catalog/pg_proc.dat      |  6 ++++--
 src/backend/catalog/system_views.sql |  6 +++++-
 src/backend/commands/sequence.c      | 24 ++++++++++++++++++------
 src/bin/pg_dump/pg_dump.c            | 16 +++++++++++++---
 src/test/regress/expected/rules.out  |  7 ++++++-
 5 files changed, 46 insertions(+), 13 deletions(-)

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6a5476d3c4..d7f17878c4 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3325,9 +3325,11 @@
   proargmodes => '{i,o,o,o,o,o,o,o}',
   proargnames => '{sequence_oid,start_value,minimum_value,maximum_value,increment,cycle_option,cache_size,data_type}',
   prosrc => 'pg_sequence_parameters' },
-{ oid => '4032', descr => 'sequence last value',
+{ oid => '4032', descr => 'sequence last value data',
   proname => 'pg_sequence_last_value', provolatile => 'v', proparallel => 'u',
-  prorettype => 'int8', proargtypes => 'regclass',
+  prorettype => 'record', proargtypes => 'regclass',
+  proallargtypes => '{regclass,bool,int8}', proargmodes => '{i,o,o}',
+  proargnames => '{seqid,is_called,last_value}',
   prosrc => 'pg_sequence_last_value' },
 
 { oid => '275', descr => 'return the next oid for a system table',
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index efb29adeb3..c52fe09501 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -178,7 +178,11 @@ CREATE VIEW pg_sequences AS
         S.seqcache AS cache_size,
         CASE
             WHEN has_sequence_privilege(C.oid, 'SELECT,USAGE'::text)
-                THEN pg_sequence_last_value(C.oid)
+                THEN (SELECT
+                          CASE WHEN sl.is_called
+                              THEN sl.last_value ELSE NULL
+                          END
+                      FROM pg_sequence_last_value(C.oid) sl)
             ELSE NULL
         END AS last_value
     FROM pg_sequence S JOIN pg_class C ON (C.oid = S.seqrelid)
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 28f8522264..73d79a7a42 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1774,11 +1774,19 @@ pg_sequence_parameters(PG_FUNCTION_ARGS)
 Datum
 pg_sequence_last_value(PG_FUNCTION_ARGS)
 {
+#define PG_SEQUENCE_LAST_VALUE_COLS		2
 	Oid			relid = PG_GETARG_OID(0);
+	Datum		values[PG_SEQUENCE_LAST_VALUE_COLS] = {0};
+	bool		nulls[PG_SEQUENCE_LAST_VALUE_COLS] = {0};
 	SeqTable	elm;
 	Relation	seqrel;
 	bool		is_called = false;
-	int64		result = 0;
+	int64		last_value = 0;
+	TupleDesc	tupdesc;
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
 
 	/* open and lock sequence */
 	init_sequence(relid, &elm, &seqrel);
@@ -1807,16 +1815,20 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		seq = read_seq_tuple(seqrel, &buf, &seqtuple);
 
 		is_called = seq->is_called;
-		result = seq->last_value;
+		last_value = seq->last_value;
 
 		UnlockReleaseBuffer(buf);
 	}
+	else
+	{
+		nulls[0] = true;
+		nulls[1] = true;
+	}
 	sequence_close(seqrel, NoLock);
 
-	if (is_called)
-		PG_RETURN_INT64(result);
-	else
-		PG_RETURN_NULL();
+	values[0] = BoolGetDatum(is_called);
+	values[1] = Int64GetDatum(last_value);
+	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
 }
 
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e324070828..4b1674c5df 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -17562,9 +17562,19 @@ dumpSequenceData(Archive *fout, const TableDataInfo *tdinfo)
 	bool		called;
 	PQExpBuffer query = createPQExpBuffer();
 
-	appendPQExpBuffer(query,
-					  "SELECT last_value, is_called FROM %s",
-					  fmtQualifiedDumpable(tbinfo));
+	/*
+	 * In versions 17 and up, pg_sequence_last_value() has been switched to
+	 * return a tuple with last_value and is_called.
+	 */
+	if (fout->remoteVersion >= 170000)
+		appendPQExpBuffer(query,
+						  "SELECT last_value, is_called "
+						  "FROM pg_sequence_last_value('%s')",
+						  fmtQualifiedDumpable(tbinfo));
+	else
+		appendPQExpBuffer(query,
+						  "SELECT last_value, is_called FROM %s",
+						  fmtQualifiedDumpable(tbinfo));
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 13178e2b3d..9541f4cfc0 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1700,7 +1700,12 @@ pg_sequences| SELECT n.nspname AS schemaname,
     s.seqcycle AS cycle,
     s.seqcache AS cache_size,
         CASE
-            WHEN has_sequence_privilege(c.oid, 'SELECT,USAGE'::text) THEN pg_sequence_last_value((c.oid)::regclass)
+            WHEN has_sequence_privilege(c.oid, 'SELECT,USAGE'::text) THEN ( SELECT
+                    CASE
+                        WHEN sl.is_called THEN sl.last_value
+                        ELSE NULL::bigint
+                    END AS "case"
+               FROM pg_sequence_last_value((c.oid)::regclass) sl(is_called, last_value))
             ELSE NULL::bigint
         END AS last_value
    FROM ((pg_sequence s
-- 
2.45.1

v6-0002-Remove-FormData_pg_sequence_data-from-init_params.patchtext/x-diff; charset=us-asciiDownload
From d542e84cd4705170785dce99c6dbbcc8ce095293 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:00:45 +0900
Subject: [PATCH v6 2/8] Remove FormData_pg_sequence_data from
 init_params()/sequence.c

init_params() sets up "last_value" and "is_called" for a sequence, based
on the sequence properties in pg_sequences.  This simplifies the logic
around log_cnt, which is reset to 0 when the metadata of a sequence is
expected to start from afresh when its properties are updated.
---
 src/backend/commands/sequence.c | 81 ++++++++++++++++++++-------------
 1 file changed, 49 insertions(+), 32 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 73d79a7a42..5bc7a74d5f 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -106,7 +106,9 @@ static Form_pg_sequence_data read_seq_tuple(Relation rel,
 static void init_params(ParseState *pstate, List *options, bool for_identity,
 						bool isInit,
 						Form_pg_sequence seqform,
-						Form_pg_sequence_data seqdataform,
+						int64 *last_value,
+						bool *reset_state,
+						bool *is_called,
 						bool *need_seq_rewrite,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
@@ -121,7 +123,9 @@ ObjectAddress
 DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 {
 	FormData_pg_sequence seqform;
-	FormData_pg_sequence_data seqdataform;
+	int64		last_value;
+	bool		reset_state;
+	bool		is_called;
 	bool		need_seq_rewrite;
 	List	   *owned_by;
 	CreateStmt *stmt = makeNode(CreateStmt);
@@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* Check and set all option values */
 	init_params(pstate, seq->options, seq->for_identity, true,
-				&seqform, &seqdataform,
+				&seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/*
@@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 		{
 			case SEQ_COL_LASTVAL:
 				coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
-				value[i - 1] = Int64GetDatumFast(seqdataform.last_value);
+				value[i - 1] = Int64GetDatumFast(last_value);
 				break;
 			case SEQ_COL_LOG:
 				coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
@@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	ObjectAddress address;
 	Relation	rel;
 	HeapTuple	seqtuple;
+	bool		reset_state = false;
+	bool		is_called;
+	int64		last_value;
 	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
@@ -481,12 +488,14 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	/* 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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
-				seqform, newdataform,
+				seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/* If needed, rewrite the sequence relation itself */
@@ -513,6 +522,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * 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);
 	}
 
@@ -1229,17 +1242,19 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 /*
  * init_params: process the options list of CREATE or ALTER SEQUENCE, and
  * store the values into appropriate fields of seqform, for changes that go
- * into the pg_sequence catalog, and fields of seqdataform for changes to the
- * sequence relation itself.  Set *need_seq_rewrite to true if we changed any
- * parameters that require rewriting the sequence's relation (interesting for
- * ALTER SEQUENCE).  Also set *owned_by to any OWNED BY option, or to NIL if
- * there is none.
+ * into the pg_sequence catalog, and fields for changes to the sequence
+ * relation itself (is_called, last_value or any state it may hold).  Set
+ * *need_seq_rewrite to true if we changed any parameters that require
+ * rewriting the sequence's relation (interesting for ALTER SEQUENCE).  Also
+ * set *owned_by to any OWNED BY option, or to NIL if there is none.  Set
+ * *reset_state if the internal state of the sequence needs to change on a
+ * follow-up nextval().
  *
  * If isInit is true, fill any unspecified options with default values;
  * otherwise, do not change existing options that aren't explicitly overridden.
  *
  * Note: we force a sequence rewrite whenever we change parameters that affect
- * generation of future sequence values, even if the seqdataform per se is not
+ * generation of future sequence values, even if the metadata per se is not
  * changed.  This allows ALTER SEQUENCE to behave transactionally.  Currently,
  * the only option that doesn't cause that is OWNED BY.  It's *necessary* for
  * ALTER SEQUENCE OWNED BY to not rewrite the sequence, because that would
@@ -1250,7 +1265,9 @@ static void
 init_params(ParseState *pstate, List *options, bool for_identity,
 			bool isInit,
 			Form_pg_sequence seqform,
-			Form_pg_sequence_data seqdataform,
+			int64 *last_value,
+			bool *reset_state,
+			bool *is_called,
 			bool *need_seq_rewrite,
 			List **owned_by)
 {
@@ -1353,11 +1370,11 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	}
 
 	/*
-	 * We must reset log_cnt when isInit or when changing any parameters that
-	 * would affect future nextval allocations.
+	 * We must reset the state when isInit or when changing any parameters
+	 * that would affect future nextval allocations.
 	 */
 	if (isInit)
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 
 	/* AS type */
 	if (as_type != NULL)
@@ -1406,7 +1423,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("INCREMENT must not be zero")));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1418,7 +1435,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	{
 		seqform->seqcycle = boolVal(is_cycled->arg);
 		Assert(BoolIsValid(seqform->seqcycle));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1429,7 +1446,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (max_value != NULL && max_value->arg)
 	{
 		seqform->seqmax = defGetInt64(max_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || max_value != NULL || reset_max_value)
 	{
@@ -1445,7 +1462,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmax = -1;	/* descending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate maximum value.  No need to check INT8 as seqmax is an int64 */
@@ -1461,7 +1478,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (min_value != NULL && min_value->arg)
 	{
 		seqform->seqmin = defGetInt64(min_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || min_value != NULL || reset_min_value)
 	{
@@ -1477,7 +1494,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmin = 1;	/* ascending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate minimum value.  No need to check INT8 as seqmin is an int64 */
@@ -1528,30 +1545,30 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (restart_value != NULL)
 	{
 		if (restart_value->arg != NULL)
-			seqdataform->last_value = defGetInt64(restart_value);
+			*last_value = defGetInt64(restart_value);
 		else
-			seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
-		seqdataform->log_cnt = 0;
+			*last_value = seqform->seqstart;
+		*is_called = false;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-		seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
+		*last_value = seqform->seqstart;
+		*is_called = false;
 	}
 
 	/* crosscheck RESTART (or current value, if changing MIN/MAX) */
-	if (seqdataform->last_value < seqform->seqmin)
+	if (*last_value < seqform->seqmin)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be less than MINVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmin)));
-	if (seqdataform->last_value > seqform->seqmax)
+	if (*last_value > seqform->seqmax)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be greater than MAXVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmax)));
 
 	/* CACHE */
@@ -1563,7 +1580,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("CACHE (%lld) must be greater than zero",
 							(long long) seqform->seqcache)));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-- 
2.45.1

v6-0003-Integrate-addition-of-attributes-for-sequences-wi.patchtext/x-diff; charset=us-asciiDownload
From d5cc3a8c1dd9c980d3bbbdfc24ba9c0ea5fc03c6 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v6 3/8] 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               | 29 +++++++++++++++++--
 src/backend/commands/tablecmds.c              | 10 +++++++
 src/backend/tcop/utility.c                    |  4 +++
 .../test_ddl_deparse/expected/alter_table.out | 10 +++++--
 .../expected/create_sequence_1.out            |  5 +++-
 .../expected/create_table.out                 | 15 ++++++++--
 .../test_ddl_deparse/test_ddl_deparse.c       |  3 ++
 8 files changed, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 85a62b538e..6823f595ae 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2351,6 +2351,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 5bc7a74d5f..33832d9fa8 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -136,6 +136,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;
@@ -174,7 +177,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;
@@ -198,7 +200,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;
@@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 66cda26a25..4d42ee442b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4491,6 +4491,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 */
@@ -4802,6 +4803,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,
@@ -5227,6 +5235,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,
@@ -6404,6 +6413,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 fa66b8017e..b64acfb23f 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1664,7 +1664,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 6daa186a84..24b2179455 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:  DDL test: type simple, tag ALTER SEQUENCE
@@ -76,7 +79,10 @@ NOTICE:    subcommand: type SET NOT NULL desc column a of table parent
 NOTICE:    subcommand: type SET NOT NULL desc column a of table child
 NOTICE:    subcommand: type SET NOT NULL desc column a of table grandchild
 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_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
index 5837ea484e..310ce5a6ba 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.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 2178ce83e9..2be95f99f8 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 67ff2b6367..61236a6343 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -114,6 +114,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.45.1

v6-0004-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From 8550949aa40ab2613a93a95457240cc38e3d922c Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 25 Apr 2024 16:49:47 +0900
Subject: [PATCH v6 4/8] 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.
- seqlocalxlog.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/seqlocalam.h               |  58 ++
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 .../rmgrdesc/{seqdesc.c => seqlocaldesc.c}    |  20 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/meson.build       |   2 +
 src/backend/access/sequence/seqlocalam.c      | 634 ++++++++++++++++++
 src/backend/access/sequence/seqlocalxlog.c    |  82 +++
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 621 +----------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 14 files changed, 817 insertions(+), 614 deletions(-)
 create mode 100644 src/include/access/seqlocalam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%)
 create mode 100644 src/backend/access/sequence/seqlocalam.c
 create mode 100644 src/backend/access/sequence/seqlocalxlog.c

diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 78e6b908c6..fa94beb6ed 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/seqlocalam.h b/src/include/access/seqlocalam.h
new file mode 100644
index 0000000000..225fb9a2cb
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2024, 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 "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_SEQ_LOCAL_LOG			0x00
+
+typedef struct xl_seq_local_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_seq_local_rec;
+
+/*
+ * 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;
+
+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);
+
+/* 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);
+extern void seq_local_change_persistence(Relation rel,
+										 char newrelpersistence);
+
+#endif							/* SEQLOCALAM_H */
diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile
index cd95eec37f..e5900ed77a 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 e8b7a65fc7..e390d385bf 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 cf0e02ded5..409c67335e 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.c
  *
  * Portions Copyright (c) 1996-2024, 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.h"
+#include "access/seqlocalam.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 9f9d31f542..a15ceec1c0 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 1843aed38d..4d24f900b6 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2024, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'seqlocalam.c',
+  'seqlocalxlog.c',
   'sequence.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
new file mode 100644
index 0000000000..b0a0d72966
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -0,0 +1,634 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.c
+ *	  Local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, 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/multixact.h"
+#include "access/seqlocalam.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"
+
+
+/*
+ * 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
+
+/* 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, (Item) 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((char *) &xlrec, sizeof(xl_seq_local_rec));
+		XLogRegisterData((char *) 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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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((char *) &xlrec, sizeof(xl_seq_local_rec));
+		XLogRegisterData((char *) 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 "heap";
+}
+
+/*
+ * 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((char *) &xlrec, sizeof(xl_seq_local_rec));
+		XLogRegisterData((char *) 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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_seq_local_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	*last_value = seq->last_value;
+	*is_called = seq->is_called;
+
+	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/sequence/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c
new file mode 100644
index 0000000000..db0ad969db
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalxlog.c
@@ -0,0 +1,82 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalxlog.c
+ *	  WAL replay logic for local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/access/sequence/seqlocalxlog.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/seqlocalam.h"
+#include "access/xlogutils.h"
+#include "storage/block.h"
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+seq_local_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+seq_local_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record);
+	seq_local_magic *sm;
+
+	if (info != XLOG_SEQ_LOCAL_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = (Page) BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	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_local_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec);
+
+	if (PageAddItem(localpage, (Item) item, itemsz,
+					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "seq_local_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 1b7499726e..cc92268937 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 33832d9fa8..2ba572958d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -50,23 +51,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -96,13 +80,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,
@@ -134,14 +114,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
@@ -174,35 +148,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;
@@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	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)
@@ -292,10 +222,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;
@@ -306,7 +232,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))
@@ -315,40 +240,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 */
@@ -357,106 +250,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, (Item) 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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) tuple->t_data, tuple->t_len);
-
-		recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	UnlockReleaseBuffer(buf);
-}
-
 /*
  * AlterSequence
  *
@@ -468,10 +261,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;
@@ -480,7 +270,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	bool		reset_state = false;
 	bool		is_called;
 	int64		last_value;
-	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
 	relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -507,16 +296,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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -526,32 +307,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 */
@@ -580,8 +339,6 @@ SequenceChangePersistence(Oid relid, char newrelpersistence)
 {
 	SeqTable	elm;
 	Relation	seqrel;
-	Buffer		buf;
-	HeapTupleData seqdatatuple;
 
 	init_sequence(relid, &elm, &seqrel);
 
@@ -589,10 +346,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);
 }
@@ -655,24 +409,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);
@@ -717,105 +462,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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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;
@@ -825,69 +474,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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) 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;
@@ -977,9 +563,6 @@ do_setval(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,
@@ -1013,9 +596,6 @@ do_setval(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),
@@ -1037,37 +617,8 @@ do_setval(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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) 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);
 }
@@ -1208,62 +759,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
@@ -1850,22 +1345,14 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	if (!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;
-		last_value = seq->last_value;
-
-		UnlockReleaseBuffer(buf);
+		seq_local_get_state(seqrel, &last_value, &is_called);
 	}
 	else
 	{
 		nulls[0] = true;
 		nulls[1] = true;
 	}
+
 	sequence_close(seqrel, NoLock);
 
 	values[0] = BoolGetDatum(is_called);
@@ -1873,57 +1360,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
 }
 
-
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = (Page) BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, (Item) item, itemsz,
-					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1938,14 +1374,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c76..8d1195de26 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/seqlocaldesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index 6b8c17bb4c..2fccc7a4e2 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index 578e473139..0b911048e1 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.45.1

v6-0005-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From 79163a333923e10e8f057d476b6713bf371e6657 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 25 Apr 2024 17:15:20 +0900
Subject: [PATCH v6 5/8] 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               |  15 --
 src/include/access/sequenceam.h               | 181 ++++++++++++++++++
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/seqlocalam.c      |  41 +++-
 src/backend/access/sequence/sequence.c        |   3 +-
 src/backend/access/sequence/sequenceamapi.c   | 145 ++++++++++++++
 src/backend/catalog/heap.c                    |   6 +-
 src/backend/commands/amcmds.c                 |  16 ++
 src/backend/commands/sequence.c               |  21 +-
 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_tables.c           |  12 ++
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/bin/psql/describe.c                       |   2 +
 src/bin/psql/tab-complete.c                   |   4 +-
 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/sql/create_am.sql            |  24 ++-
 src/test/regress/sql/opr_sanity.sql           |  10 +
 src/tools/pgindent/typedefs.list              |   5 +-
 38 files changed, 682 insertions(+), 169 deletions(-)
 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
index 225fb9a2cb..21936511ac 100644
--- a/src/include/access/seqlocalam.h
+++ b/src/include/access/seqlocalam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_SEQ_LOCAL_LOG			0x00
@@ -41,18 +40,4 @@ 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);
 
-/* 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);
-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 0000000000..ac48c8b468
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,181 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2024, 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.
+	 */
+	void		(*get_state) (Relation rel, int64 *last_value, bool *is_called);
+
+	/*
+	 * 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)
+{
+	rel->rd_sequenceam->get_state(rel, last_value, is_called);
+}
+
+/*
+ * 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 db87490282..d991a77604 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 => '8048', 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 475593fad4..3a94e8c636 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 0fc2c093b0..21623f34fa 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -228,6 +228,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)
+
 extern int	errdetail_relkind_not_supported(char relkind);
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d7f17878c4..17890547c3 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',
@@ -7629,6 +7635,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 ceff66ccde..0eba013687 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -627,6 +627,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 29c511e319..39d2cae422 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -141,6 +141,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 e88cbee3b5..9b22b2f248 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index b665e55b65..774ae2652c 100644
--- a/src/include/nodes/meson.build
+++ b/src/include/nodes/meson.build
@@ -9,6 +9,7 @@ node_support_input_i = [
   '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/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6823f595ae..2c18a34c00 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3127,6 +3127,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 d64dc5fcdb..539ca97003 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -55,6 +55,8 @@ extern bool check_debug_io_direct(char **newval, void **extra, GucSource source)
 extern void assign_debug_io_direct(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 8700204953..2a4b7bed2b 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 a15ceec1c0..62006165a1 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = seqlocalam.o seqlocalxlog.o sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 4d24f900b6..ea91990552 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -4,4 +4,5 @@ backend_sources += files(
   'seqlocalam.c',
   'seqlocalxlog.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
index b0a0d72966..2e524c9e99 100644
--- a/src/backend/access/sequence/seqlocalam.c
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -17,6 +17,7 @@
 
 #include "access/multixact.h"
 #include "access/seqlocalam.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -24,6 +25,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -230,10 +232,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;
@@ -417,7 +419,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 "heap";
@@ -432,7 +434,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];
@@ -499,7 +501,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;
@@ -547,7 +549,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;
@@ -600,7 +602,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)
 {
 	Buffer		buf;
@@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called)
  *
  * Persistence change for the local sequence Relation.
  */
-void
+static void
 seq_local_change_persistence(Relation rel, char newrelpersistence)
 {
 	Buffer		buf;
@@ -632,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 8d6b7bb5dc..5e7d76d3b0 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 0000000000..dd1a60d827
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2024, 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/xact.h"
+#include "access/sequenceam.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 a122bbffce..e1308916eb 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1464,8 +1464,12 @@ 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.
+		 *
+		 * 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)
+		if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE) ||
+			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 aaa0f9a1dc..1a27f191a3 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 2ba572958d..d085b45632 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -152,6 +152,7 @@ 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;
@@ -173,7 +174,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)
@@ -241,7 +242,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 */
@@ -297,7 +298,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
 
 	/* Read sequence data */
-	seq_local_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -310,7 +311,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 */
@@ -346,7 +347,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);
 }
@@ -463,8 +464,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;
@@ -618,7 +619,7 @@ do_setval(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);
 }
@@ -1345,7 +1346,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 	if (!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		seq_local_get_state(seqrel, &last_value, &is_called);
+		sequence_get_state(seqrel, &last_value, &is_called);
 	}
 	else
 	{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 4d42ee442b..f68b38184b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -22,6 +22,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"
@@ -947,14 +948,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)
 	{
@@ -967,6 +972,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 constraints are passed in
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 66bbad8e6e..86b1773c0b 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -47,6 +47,7 @@ node_headers = \
 	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/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 81df3bdf95..d2450e559d 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -59,6 +59,7 @@ my @all_input_files = qw(
   nodes/execnodes.h
   access/amapi.h
   access/sdir.h
+  access/sequenceam.h
   access/tableam.h
   access/tsmapi.h
   commands/event_trigger.h
@@ -83,6 +84,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 4d582950b7..9d75ec555e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -391,6 +391,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
@@ -4883,23 +4884,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;
@@ -4936,6 +4940,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); }
 		;
@@ -5932,6 +5941,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 d5c2b2ff0b..052f829d17 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"
@@ -464,6 +465,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence->relpersistence = cxt->rel ? cxt->rel->rd_rel->relpersistence : cxt->relation->relpersistence;
 
 	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 e189e9b79d..e31dbb0ebb 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 35dbb87ae3..aa210618e1 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"
@@ -301,6 +303,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);
@@ -1206,8 +1209,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)
 	{
@@ -1216,6 +1218,8 @@ retry:
 		 * inherit.
 		 */
 	}
+	else if (RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind))
+		RelationInitSequenceAccessMethod(relation);
 	else
 		Assert(relation->rd_rel->relam == InvalidOid);
 
@@ -1812,17 +1816,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.
@@ -1853,6 +1849,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
  *
@@ -3710,14 +3749,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);
 
 	/*
 	 * Okay to insert into the relcache hash table.
@@ -4332,13 +4374,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);
@@ -6325,8 +6375,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);
@@ -6338,6 +6390,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_tables.c b/src/backend/utils/misc/guc_tables.c
index 46c258be28..4a181b6294 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -30,6 +30,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"
@@ -4118,6 +4119,17 @@ struct config_string ConfigureNamesString[] =
 		check_default_table_access_method, NULL, NULL
 	},
 
+	{
+		{"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets the default sequence access method for new sequences."),
+			NULL,
+			GUC_IS_NAME
+		},
+		&default_sequence_access_method,
+		DEFAULT_SEQUENCE_ACCESS_METHOD,
+		check_default_sequence_access_method, NULL, NULL
+	},
+
 	{
 		{"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default tablespace to create tables and indexes in."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index e0567de219..f9d0cadca1 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -703,6 +703,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 f67bf0b892..46b1126618 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -161,10 +161,12 @@ describeAccessMethods(const char *pattern, bool verbose)
 					  "SELECT amname AS \"%s\",\n"
 					  "  CASE amtype"
 					  " WHEN 'i' THEN '%s'"
+					  " WHEN 's' THEN '%s'"
 					  " WHEN 't' 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.c b/src/bin/psql/tab-complete.c
index d453e224d9..22a29dc4bd 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2200,7 +2200,7 @@ psql_completion(const char *text, int start, int end)
 	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");
@@ -3240,7 +3240,7 @@ psql_completion(const char *text, int start, int end)
 	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 35d4cf1d46..f1ad1c583f 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;
@@ -500,9 +495,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;
@@ -519,18 +517,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
@@ -560,3 +558,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 9d047b21b8..ffb4c66853 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1931,6 +1931,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 3bbe4c5f97..e64e50bbb9 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4962,31 +4962,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
@@ -5011,32 +5013,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/sql/create_am.sql b/src/test/regress/sql/create_am.sql
index 825aed325e..472289994e 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;
@@ -317,9 +314,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;
@@ -355,3 +356,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 2fe7b6dcc4..1409622374 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1229,6 +1229,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/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 61ad417cde..84ef66a0ef 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2569,6 +2569,7 @@ SeqScan
 SeqScanState
 SeqTable
 SeqTableData
+SequenceAmRoutine
 SerCommitSeqNo
 SerialControl
 SerialIOData
@@ -3583,6 +3584,7 @@ lineno_t
 list_sort_comparator
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 local_ts_iter
 local_ts_radix_tree
@@ -3862,7 +3864,6 @@ scram_state
 scram_state_enum
 security_class_t
 sem_t
-sepgsql_context_info_t
 sequence_magic
 set_join_pathlist_hook_type
 set_rel_pathlist_hook_type
@@ -4083,6 +4084,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
@@ -4094,7 +4096,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.45.1

#24Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#23)
8 attachment(s)
Re: Sequence Access Methods, round two

On Thu, Jun 20, 2024 at 03:12:32PM +0900, Michael Paquier wrote:

While on it, I have noticed a couple of conflicts while rebasing, so
attached is a refreshed patch set.

Please find attached a new patch set for the next commit fest. The
patch has required a bit of work to be able to work on HEAD,
particularly around the fact that pg_sequence_read_tuple() is able to
do the same work as the modifications done for pg_sequence_last_value()
in the previous patch sets. I have modified the patch set to depend
on that, and adapted pg_dump/restore to it. The dump/restore part has
also required some tweaks to make sure that the AM is dumped depending
on if --schema-only and if we care about the values.

Finally, I have been rather annoyed by the addition of log_cnt in the
new function pg_sequence_read_tuple(). This patch set could also
implement a new system function, but it looks like a waste as we don't
care about log_cnt in pg_dump and pg_upgrade on HEAD, so I'm proposing
to remove it on a different thread:
/messages/by-id/Zsvka3r-y2ZoXAdH@paquier.xyz
--
Michael

Attachments:

v7-0001-Remove-log_cnt-from-pg_sequence_read_tuple.patchtext/x-diff; charset=us-asciiDownload
From b7b2f21e23157f1c744ef87651bdc8e0da68f713 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Aug 2024 11:15:21 +0900
Subject: [PATCH v7 1/8] Remove log_cnt from pg_sequence_read_tuple()

This will be used by the sequence AM patch, and log_cnt is something
that sequence AMs can optionally use, at least in this design.
---
 src/include/catalog/pg_proc.dat        |  4 ++--
 src/backend/commands/sequence.c        | 15 +++++++--------
 src/test/regress/expected/sequence.out |  6 +++---
 3 files changed, 12 insertions(+), 13 deletions(-)

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4abc6d9526..986df08c6e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3332,8 +3332,8 @@
 { oid => '9876', descr => 'return sequence tuple, for use by pg_dump',
   proname => 'pg_sequence_read_tuple', provolatile => 'v', proparallel => 'u',
   prorettype => 'record', proargtypes => 'regclass',
-  proallargtypes => '{regclass,int8,int8,bool}', proargmodes => '{i,o,o,o}',
-  proargnames => '{sequence_oid,last_value,log_cnt,is_called}',
+  proallargtypes => '{regclass,int8,bool}', proargmodes => '{i,o,o}',
+  proargnames => '{sequence_oid,last_value,is_called}',
   prosrc => 'pg_sequence_read_tuple' },
 
 { oid => '275', descr => 'return the next oid for a system table',
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 8c1131f020..11000b97bd 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1783,21 +1783,20 @@ pg_sequence_parameters(PG_FUNCTION_ARGS)
 Datum
 pg_sequence_read_tuple(PG_FUNCTION_ARGS)
 {
+#define SEQUENCE_READ_TUPLE_COLS	2
 	Oid			relid = PG_GETARG_OID(0);
 	SeqTable	elm;
 	Relation	seqrel;
-	Datum		values[SEQ_COL_LASTCOL] = {0};
-	bool		isnull[SEQ_COL_LASTCOL] = {0};
+	Datum		values[SEQUENCE_READ_TUPLE_COLS] = {0};
+	bool		isnull[SEQUENCE_READ_TUPLE_COLS] = {0};
 	TupleDesc	resultTupleDesc;
 	HeapTuple	resultHeapTuple;
 	Datum		result;
 
-	resultTupleDesc = CreateTemplateTupleDesc(SEQ_COL_LASTCOL);
+	resultTupleDesc = CreateTemplateTupleDesc(SEQUENCE_READ_TUPLE_COLS);
 	TupleDescInitEntry(resultTupleDesc, (AttrNumber) 1, "last_value",
 					   INT8OID, -1, 0);
-	TupleDescInitEntry(resultTupleDesc, (AttrNumber) 2, "log_cnt",
-					   INT8OID, -1, 0);
-	TupleDescInitEntry(resultTupleDesc, (AttrNumber) 3, "is_called",
+	TupleDescInitEntry(resultTupleDesc, (AttrNumber) 2, "is_called",
 					   BOOLOID, -1, 0);
 	resultTupleDesc = BlessTupleDesc(resultTupleDesc);
 
@@ -1818,8 +1817,7 @@ pg_sequence_read_tuple(PG_FUNCTION_ARGS)
 		seq = read_seq_tuple(seqrel, &buf, &seqtuple);
 
 		values[0] = Int64GetDatum(seq->last_value);
-		values[1] = Int64GetDatum(seq->log_cnt);
-		values[2] = BoolGetDatum(seq->is_called);
+		values[1] = BoolGetDatum(seq->is_called);
 
 		UnlockReleaseBuffer(buf);
 	}
@@ -1831,6 +1829,7 @@ pg_sequence_read_tuple(PG_FUNCTION_ARGS)
 	resultHeapTuple = heap_form_tuple(resultTupleDesc, values, isnull);
 	result = HeapTupleGetDatum(resultHeapTuple);
 	PG_RETURN_DATUM(result);
+#undef SEQUENCE_READ_TUPLE_COLS
 }
 
 
diff --git a/src/test/regress/expected/sequence.out b/src/test/regress/expected/sequence.out
index e749c4574e..6be5406ae9 100644
--- a/src/test/regress/expected/sequence.out
+++ b/src/test/regress/expected/sequence.out
@@ -841,9 +841,9 @@ SELECT nextval('test_seq1');
 
 -- pg_sequence_read_tuple
 SELECT * FROM pg_sequence_read_tuple('test_seq1');
- last_value | log_cnt | is_called 
-------------+---------+-----------
-         10 |      32 | t
+ last_value | is_called 
+------------+-----------
+         10 | t
 (1 row)
 
 DROP SEQUENCE test_seq1;
-- 
2.45.2

v7-0002-Remove-FormData_pg_sequence_data-from-init_params.patchtext/x-diff; charset=us-asciiDownload
From fdc6a51e6a81f29b53e949285eb7d1d4024fb0aa Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:00:45 +0900
Subject: [PATCH v7 2/8] Remove FormData_pg_sequence_data from
 init_params()/sequence.c

init_params() sets up "last_value" and "is_called" for a sequence, based
on the sequence properties in pg_sequences.  This simplifies the logic
around log_cnt, which is reset to 0 when the metadata of a sequence is
expected to start from afresh when its properties are updated.
---
 src/backend/commands/sequence.c | 81 ++++++++++++++++++++-------------
 1 file changed, 49 insertions(+), 32 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 11000b97bd..e4c0937435 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -106,7 +106,9 @@ static Form_pg_sequence_data read_seq_tuple(Relation rel,
 static void init_params(ParseState *pstate, List *options, bool for_identity,
 						bool isInit,
 						Form_pg_sequence seqform,
-						Form_pg_sequence_data seqdataform,
+						int64 *last_value,
+						bool *reset_state,
+						bool *is_called,
 						bool *need_seq_rewrite,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
@@ -121,7 +123,9 @@ ObjectAddress
 DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 {
 	FormData_pg_sequence seqform;
-	FormData_pg_sequence_data seqdataform;
+	int64		last_value;
+	bool		reset_state;
+	bool		is_called;
 	bool		need_seq_rewrite;
 	List	   *owned_by;
 	CreateStmt *stmt = makeNode(CreateStmt);
@@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* Check and set all option values */
 	init_params(pstate, seq->options, seq->for_identity, true,
-				&seqform, &seqdataform,
+				&seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/*
@@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 		{
 			case SEQ_COL_LASTVAL:
 				coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
-				value[i - 1] = Int64GetDatumFast(seqdataform.last_value);
+				value[i - 1] = Int64GetDatumFast(last_value);
 				break;
 			case SEQ_COL_LOG:
 				coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
@@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	ObjectAddress address;
 	Relation	rel;
 	HeapTuple	seqtuple;
+	bool		reset_state = false;
+	bool		is_called;
+	int64		last_value;
 	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
@@ -481,12 +488,14 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	/* 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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
-				seqform, newdataform,
+				seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/* If needed, rewrite the sequence relation itself */
@@ -513,6 +522,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * 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);
 	}
 
@@ -1236,17 +1249,19 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 /*
  * init_params: process the options list of CREATE or ALTER SEQUENCE, and
  * store the values into appropriate fields of seqform, for changes that go
- * into the pg_sequence catalog, and fields of seqdataform for changes to the
- * sequence relation itself.  Set *need_seq_rewrite to true if we changed any
- * parameters that require rewriting the sequence's relation (interesting for
- * ALTER SEQUENCE).  Also set *owned_by to any OWNED BY option, or to NIL if
- * there is none.
+ * into the pg_sequence catalog, and fields for changes to the sequence
+ * relation itself (is_called, last_value or any state it may hold).  Set
+ * *need_seq_rewrite to true if we changed any parameters that require
+ * rewriting the sequence's relation (interesting for ALTER SEQUENCE).  Also
+ * set *owned_by to any OWNED BY option, or to NIL if there is none.  Set
+ * *reset_state if the internal state of the sequence needs to change on a
+ * follow-up nextval().
  *
  * If isInit is true, fill any unspecified options with default values;
  * otherwise, do not change existing options that aren't explicitly overridden.
  *
  * Note: we force a sequence rewrite whenever we change parameters that affect
- * generation of future sequence values, even if the seqdataform per se is not
+ * generation of future sequence values, even if the metadata per se is not
  * changed.  This allows ALTER SEQUENCE to behave transactionally.  Currently,
  * the only option that doesn't cause that is OWNED BY.  It's *necessary* for
  * ALTER SEQUENCE OWNED BY to not rewrite the sequence, because that would
@@ -1257,7 +1272,9 @@ static void
 init_params(ParseState *pstate, List *options, bool for_identity,
 			bool isInit,
 			Form_pg_sequence seqform,
-			Form_pg_sequence_data seqdataform,
+			int64 *last_value,
+			bool *reset_state,
+			bool *is_called,
 			bool *need_seq_rewrite,
 			List **owned_by)
 {
@@ -1360,11 +1377,11 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	}
 
 	/*
-	 * We must reset log_cnt when isInit or when changing any parameters that
-	 * would affect future nextval allocations.
+	 * We must reset the state when isInit or when changing any parameters
+	 * that would affect future nextval allocations.
 	 */
 	if (isInit)
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 
 	/* AS type */
 	if (as_type != NULL)
@@ -1413,7 +1430,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("INCREMENT must not be zero")));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1425,7 +1442,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	{
 		seqform->seqcycle = boolVal(is_cycled->arg);
 		Assert(BoolIsValid(seqform->seqcycle));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1436,7 +1453,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (max_value != NULL && max_value->arg)
 	{
 		seqform->seqmax = defGetInt64(max_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || max_value != NULL || reset_max_value)
 	{
@@ -1452,7 +1469,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmax = -1;	/* descending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate maximum value.  No need to check INT8 as seqmax is an int64 */
@@ -1468,7 +1485,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (min_value != NULL && min_value->arg)
 	{
 		seqform->seqmin = defGetInt64(min_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || min_value != NULL || reset_min_value)
 	{
@@ -1484,7 +1501,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmin = 1;	/* ascending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate minimum value.  No need to check INT8 as seqmin is an int64 */
@@ -1535,30 +1552,30 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (restart_value != NULL)
 	{
 		if (restart_value->arg != NULL)
-			seqdataform->last_value = defGetInt64(restart_value);
+			*last_value = defGetInt64(restart_value);
 		else
-			seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
-		seqdataform->log_cnt = 0;
+			*last_value = seqform->seqstart;
+		*is_called = false;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-		seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
+		*last_value = seqform->seqstart;
+		*is_called = false;
 	}
 
 	/* crosscheck RESTART (or current value, if changing MIN/MAX) */
-	if (seqdataform->last_value < seqform->seqmin)
+	if (*last_value < seqform->seqmin)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be less than MINVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmin)));
-	if (seqdataform->last_value > seqform->seqmax)
+	if (*last_value > seqform->seqmax)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be greater than MAXVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmax)));
 
 	/* CACHE */
@@ -1570,7 +1587,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("CACHE (%lld) must be greater than zero",
 							(long long) seqform->seqcache)));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-- 
2.45.2

v7-0003-Integrate-addition-of-attributes-for-sequences-wi.patchtext/x-diff; charset=us-asciiDownload
From 9505540258c7d7d4bbafc4948af5d9945e4a9899 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v7 3/8] 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               | 29 +++++++++++++++++--
 src/backend/commands/tablecmds.c              | 10 +++++++
 src/backend/tcop/utility.c                    |  4 +++
 .../test_ddl_deparse/expected/alter_table.out | 10 +++++--
 .../expected/create_sequence_1.out            |  5 +++-
 .../expected/create_table.out                 | 15 ++++++++--
 .../test_ddl_deparse/test_ddl_deparse.c       |  3 ++
 8 files changed, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 124d853e49..f5fe0d2257 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2338,6 +2338,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 e4c0937435..1a4fe65dbf 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -136,6 +136,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;
@@ -174,7 +177,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;
@@ -198,7 +200,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;
@@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 dac39df83a..f6e3873b29 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4479,6 +4479,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 */
@@ -4782,6 +4783,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,
@@ -5197,6 +5205,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,
@@ -6358,6 +6367,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 b2ea8125c9..6a65db0910 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1664,7 +1664,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 6daa186a84..24b2179455 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:  DDL test: type simple, tag ALTER SEQUENCE
@@ -76,7 +79,10 @@ NOTICE:    subcommand: type SET NOT NULL desc column a of table parent
 NOTICE:    subcommand: type SET NOT NULL desc column a of table child
 NOTICE:    subcommand: type SET NOT NULL desc column a of table grandchild
 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_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
index 5837ea484e..310ce5a6ba 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.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 2178ce83e9..2be95f99f8 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 2758ae82d7..280642e81c 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -114,6 +114,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.45.2

v7-0004-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From afb93fe43a9331a6c77a8441e961cadc08499a63 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 25 Apr 2024 16:49:47 +0900
Subject: [PATCH v7 4/8] 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.
- seqlocalxlog.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/seqlocalam.h               |  58 ++
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 .../rmgrdesc/{seqdesc.c => seqlocaldesc.c}    |  20 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/meson.build       |   2 +
 src/backend/access/sequence/seqlocalam.c      | 634 ++++++++++++++++++
 src/backend/access/sequence/seqlocalxlog.c    |  82 +++
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 634 +-----------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 14 files changed, 822 insertions(+), 622 deletions(-)
 create mode 100644 src/include/access/seqlocalam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%)
 create mode 100644 src/backend/access/sequence/seqlocalam.c
 create mode 100644 src/backend/access/sequence/seqlocalxlog.c

diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 78e6b908c6..fa94beb6ed 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/seqlocalam.h b/src/include/access/seqlocalam.h
new file mode 100644
index 0000000000..225fb9a2cb
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2024, 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 "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_SEQ_LOCAL_LOG			0x00
+
+typedef struct xl_seq_local_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_seq_local_rec;
+
+/*
+ * 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;
+
+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);
+
+/* 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);
+extern void seq_local_change_persistence(Relation rel,
+										 char newrelpersistence);
+
+#endif							/* SEQLOCALAM_H */
diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile
index cd95eec37f..e5900ed77a 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 e8b7a65fc7..e390d385bf 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 cf0e02ded5..409c67335e 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.c
  *
  * Portions Copyright (c) 1996-2024, 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.h"
+#include "access/seqlocalam.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 9f9d31f542..a15ceec1c0 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 1843aed38d..4d24f900b6 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2024, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'seqlocalam.c',
+  'seqlocalxlog.c',
   'sequence.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
new file mode 100644
index 0000000000..b0a0d72966
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -0,0 +1,634 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.c
+ *	  Local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, 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/multixact.h"
+#include "access/seqlocalam.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"
+
+
+/*
+ * 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
+
+/* 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, (Item) 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((char *) &xlrec, sizeof(xl_seq_local_rec));
+		XLogRegisterData((char *) 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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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((char *) &xlrec, sizeof(xl_seq_local_rec));
+		XLogRegisterData((char *) 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 "heap";
+}
+
+/*
+ * 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((char *) &xlrec, sizeof(xl_seq_local_rec));
+		XLogRegisterData((char *) 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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_seq_local_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	*last_value = seq->last_value;
+	*is_called = seq->is_called;
+
+	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/sequence/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c
new file mode 100644
index 0000000000..db0ad969db
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalxlog.c
@@ -0,0 +1,82 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalxlog.c
+ *	  WAL replay logic for local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/access/sequence/seqlocalxlog.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/seqlocalam.h"
+#include "access/xlogutils.h"
+#include "storage/block.h"
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+seq_local_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+seq_local_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record);
+	seq_local_magic *sm;
+
+	if (info != XLOG_SEQ_LOCAL_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = (Page) BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	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_local_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec);
+
+	if (PageAddItem(localpage, (Item) item, itemsz,
+					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "seq_local_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 1b7499726e..cc92268937 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 1a4fe65dbf..e75878d8c6 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -50,23 +51,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -96,13 +80,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,
@@ -134,14 +114,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
@@ -174,35 +148,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;
@@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	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)
@@ -292,10 +222,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;
@@ -306,7 +232,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))
@@ -315,40 +240,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 */
@@ -357,106 +250,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, (Item) 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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) tuple->t_data, tuple->t_len);
-
-		recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	UnlockReleaseBuffer(buf);
-}
-
 /*
  * AlterSequence
  *
@@ -468,10 +261,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;
@@ -480,7 +270,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	bool		reset_state = false;
 	bool		is_called;
 	int64		last_value;
-	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
 	relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -507,16 +296,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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -526,32 +307,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 */
@@ -580,8 +339,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
@@ -596,10 +353,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);
 }
@@ -662,24 +416,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);
@@ -724,105 +469,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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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;
@@ -832,69 +481,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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) 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;
@@ -984,9 +570,6 @@ do_setval(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,
@@ -1020,9 +603,6 @@ do_setval(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),
@@ -1044,37 +624,8 @@ do_setval(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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) 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);
 }
@@ -1215,62 +766,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
@@ -1852,16 +1347,13 @@ pg_sequence_read_tuple(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		Buffer		buf;
-		HeapTupleData seqtuple;
-		Form_pg_sequence_data seq;
+		bool		is_called;
+		int64		last_value;
 
-		seq = read_seq_tuple(seqrel, &buf, &seqtuple);
+		seq_local_get_state(seqrel, &last_value, &is_called);
 
-		values[0] = Int64GetDatum(seq->last_value);
-		values[1] = BoolGetDatum(seq->is_called);
-
-		UnlockReleaseBuffer(buf);
+		values[0] = Int64GetDatum(last_value);
+		values[1] = BoolGetDatum(is_called);
 	}
 	else
 		memset(isnull, true, sizeof(isnull));
@@ -1905,17 +1397,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);
 	}
+
 	sequence_close(seqrel, NoLock);
 
 	if (is_called)
@@ -1924,57 +1408,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
-
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = (Page) BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, (Item) item, itemsz,
-					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1989,14 +1422,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c76..8d1195de26 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/seqlocaldesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index 6b8c17bb4c..2fccc7a4e2 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index 578e473139..0b911048e1 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.45.2

v7-0005-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From 443f70107bb0b8e4657558637d0cca761422337b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 25 Apr 2024 17:15:20 +0900
Subject: [PATCH v7 5/8] 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               |  15 --
 src/include/access/sequenceam.h               | 181 ++++++++++++++++++
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/seqlocalam.c      |  41 +++-
 src/backend/access/sequence/sequence.c        |   3 +-
 src/backend/access/sequence/sequenceamapi.c   | 145 ++++++++++++++
 src/backend/catalog/heap.c                    |   6 +-
 src/backend/commands/amcmds.c                 |  16 ++
 src/backend/commands/sequence.c               |  23 +--
 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_tables.c           |  12 ++
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/bin/psql/describe.c                       |   2 +
 src/bin/psql/tab-complete.c                   |   4 +-
 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/sql/create_am.sql            |  24 ++-
 src/test/regress/sql/opr_sanity.sql           |  10 +
 src/tools/pgindent/typedefs.list              |   5 +-
 38 files changed, 683 insertions(+), 170 deletions(-)
 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
index 225fb9a2cb..21936511ac 100644
--- a/src/include/access/seqlocalam.h
+++ b/src/include/access/seqlocalam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_SEQ_LOCAL_LOG			0x00
@@ -41,18 +40,4 @@ 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);
 
-/* 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);
-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 0000000000..ac48c8b468
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,181 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2024, 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.
+	 */
+	void		(*get_state) (Relation rel, int64 *last_value, bool *is_called);
+
+	/*
+	 * 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)
+{
+	rel->rd_sequenceam->get_state(rel, last_value, is_called);
+}
+
+/*
+ * 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 db87490282..d991a77604 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 => '8048', 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 475593fad4..3a94e8c636 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 0fc2c093b0..21623f34fa 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -228,6 +228,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)
+
 extern int	errdetail_relkind_not_supported(char relkind);
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 986df08c6e..7c4388e000 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',
@@ -7677,6 +7683,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 ceff66ccde..0eba013687 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -627,6 +627,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 29c511e319..39d2cae422 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -141,6 +141,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 e88cbee3b5..9b22b2f248 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index b665e55b65..774ae2652c 100644
--- a/src/include/nodes/meson.build
+++ b/src/include/nodes/meson.build
@@ -9,6 +9,7 @@ node_support_input_i = [
   '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/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f5fe0d2257..6dc56e3eaf 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3112,6 +3112,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 5813dba0a2..f4a49969b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -53,6 +53,8 @@ extern bool check_debug_io_direct(char **newval, void **extra, GucSource source)
 extern void assign_debug_io_direct(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 8700204953..2a4b7bed2b 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 a15ceec1c0..62006165a1 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = seqlocalam.o seqlocalxlog.o sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 4d24f900b6..ea91990552 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -4,4 +4,5 @@ backend_sources += files(
   'seqlocalam.c',
   'seqlocalxlog.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
index b0a0d72966..2e524c9e99 100644
--- a/src/backend/access/sequence/seqlocalam.c
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -17,6 +17,7 @@
 
 #include "access/multixact.h"
 #include "access/seqlocalam.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -24,6 +25,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -230,10 +232,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;
@@ -417,7 +419,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 "heap";
@@ -432,7 +434,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];
@@ -499,7 +501,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;
@@ -547,7 +549,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;
@@ -600,7 +602,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)
 {
 	Buffer		buf;
@@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called)
  *
  * Persistence change for the local sequence Relation.
  */
-void
+static void
 seq_local_change_persistence(Relation rel, char newrelpersistence)
 {
 	Buffer		buf;
@@ -632,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 8d6b7bb5dc..5e7d76d3b0 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 0000000000..dd1a60d827
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2024, 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/xact.h"
+#include "access/sequenceam.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 01b43cc6a8..3b8d5a9989 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1472,8 +1472,12 @@ 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.
+		 *
+		 * 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)
+		if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE) ||
+			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 aaa0f9a1dc..1a27f191a3 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 e75878d8c6..1baea307ed 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -152,6 +152,7 @@ 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;
@@ -173,7 +174,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)
@@ -241,7 +242,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 */
@@ -297,7 +298,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
 
 	/* Read sequence data */
-	seq_local_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -310,7 +311,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 */
@@ -353,7 +354,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);
 }
@@ -470,8 +471,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;
@@ -625,7 +626,7 @@ do_setval(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);
 }
@@ -1350,7 +1351,7 @@ pg_sequence_read_tuple(PG_FUNCTION_ARGS)
 		bool		is_called;
 		int64		last_value;
 
-		seq_local_get_state(seqrel, &last_value, &is_called);
+		sequence_get_state(seqrel, &last_value, &is_called);
 
 		values[0] = Int64GetDatum(last_value);
 		values[1] = BoolGetDatum(is_called);
@@ -1397,7 +1398,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		seq_local_get_state(seqrel, &result, &is_called);
+		sequence_get_state(seqrel, &result, &is_called);
 	}
 
 	sequence_close(seqrel, NoLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f6e3873b29..5541948736 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -22,6 +22,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"
@@ -942,14 +943,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)
 	{
@@ -962,6 +967,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 constraints are passed in
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 66bbad8e6e..86b1773c0b 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -47,6 +47,7 @@ node_headers = \
 	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/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 81df3bdf95..d2450e559d 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -59,6 +59,7 @@ my @all_input_files = qw(
   nodes/execnodes.h
   access/amapi.h
   access/sdir.h
+  access/sequenceam.h
   access/tableam.h
   access/tsmapi.h
   commands/event_trigger.h
@@ -83,6 +84,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 84cef57a70..8e376d974a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -390,6 +390,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
@@ -4831,23 +4832,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;
@@ -4884,6 +4888,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); }
 		;
@@ -5880,6 +5889,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 79cad4ab30..e2fd33a530 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"
@@ -461,6 +462,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence->relpersistence = cxt->rel ? cxt->rel->rd_rel->relpersistence : cxt->relation->relpersistence;
 
 	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 e189e9b79d..e31dbb0ebb 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 66ed24e401..6f80d4f707 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"
@@ -301,6 +303,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);
@@ -1204,8 +1207,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)
 	{
@@ -1214,6 +1216,8 @@ retry:
 		 * inherit.
 		 */
 	}
+	else if (RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind))
+		RelationInitSequenceAccessMethod(relation);
 	else
 		Assert(relation->rd_rel->relam == InvalidOid);
 
@@ -1810,17 +1814,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.
@@ -1851,6 +1847,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
  *
@@ -3711,14 +3750,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);
 
 	/*
 	 * Okay to insert into the relcache hash table.
@@ -4333,13 +4375,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);
@@ -6326,8 +6376,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);
@@ -6339,6 +6391,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_tables.c b/src/backend/utils/misc/guc_tables.c
index af227b1f24..ef768e3437 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -30,6 +30,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"
@@ -4185,6 +4186,17 @@ struct config_string ConfigureNamesString[] =
 		check_default_table_access_method, NULL, NULL
 	},
 
+	{
+		{"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets the default sequence access method for new sequences."),
+			NULL,
+			GUC_IS_NAME
+		},
+		&default_sequence_access_method,
+		DEFAULT_SEQUENCE_ACCESS_METHOD,
+		check_default_sequence_access_method, NULL, NULL
+	},
+
 	{
 		{"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default tablespace to create tables and indexes in."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 667e0dc40a..879c03c770 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -703,6 +703,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 7c9a1f234c..98ff40f00d 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -161,10 +161,12 @@ describeAccessMethods(const char *pattern, bool verbose)
 					  "SELECT amname AS \"%s\",\n"
 					  "  CASE amtype"
 					  " WHEN 'i' THEN '%s'"
+					  " WHEN 's' THEN '%s'"
 					  " WHEN 't' 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.c b/src/bin/psql/tab-complete.c
index a7ccde6d7d..0352c73e30 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2200,7 +2200,7 @@ psql_completion(const char *text, int start, int end)
 	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");
@@ -3226,7 +3226,7 @@ psql_completion(const char *text, int start, int end)
 	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 35d4cf1d46..f1ad1c583f 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;
@@ -500,9 +495,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;
@@ -519,18 +517,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
@@ -560,3 +558,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 0d734169f1..ea1f1048ea 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1933,6 +1933,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 6aeb7cb963..beb01d78a4 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5017,31 +5017,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
@@ -5066,32 +5068,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/sql/create_am.sql b/src/test/regress/sql/create_am.sql
index 825aed325e..472289994e 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;
@@ -317,9 +314,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;
@@ -355,3 +356,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 2fe7b6dcc4..1409622374 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1229,6 +1229,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/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 9e951a9e6f..6e1911a76d 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2579,6 +2579,7 @@ SeqScanState
 SeqTable
 SeqTableData
 SeqType
+SequenceAmRoutine
 SequenceItem
 SerCommitSeqNo
 SerialControl
@@ -3596,6 +3597,7 @@ lineno_t
 list_sort_comparator
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 local_ts_iter
 local_ts_radix_tree
@@ -3878,7 +3880,6 @@ scram_state
 scram_state_enum
 security_class_t
 sem_t
-sepgsql_context_info_t
 sequence_magic
 set_join_pathlist_hook_type
 set_rel_pathlist_hook_type
@@ -4100,6 +4101,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
@@ -4111,7 +4113,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.45.2

v7-0006-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From cad4ca7b03f1adfd6ac321a43f5f1fe0508aa921 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Aug 2024 13:25:19 +0900
Subject: [PATCH v7 6/8] 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     | 49 +++++++++++++++++++++
 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, 207 insertions(+), 11 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index fbf5f1c515..4eae73c16e 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -95,6 +95,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
@@ -185,6 +186,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 8c20c263c4..0eb5f9de3e 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -174,6 +174,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;
@@ -1224,6 +1225,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;
@@ -2379,6 +2381,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 */
 
@@ -2608,6 +2611,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);
@@ -2712,6 +2716,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);
 
@@ -3367,6 +3374,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;
 
@@ -3529,6 +3539,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.
  */
@@ -3738,6 +3799,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
 	_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);
 
@@ -4262,6 +4324,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);
@@ -5001,6 +5065,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -5060,6 +5125,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 ce5ed1dd39..cdfebcd340 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)
 
@@ -322,6 +323,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 */
 
@@ -353,6 +355,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;
@@ -394,6 +397,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 b6e01d3d29..0267025d7f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -134,6 +134,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
@@ -477,6 +478,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},
@@ -1098,6 +1100,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;
@@ -1214,6 +1217,7 @@ help(const char *progname)
 	printf(_("  --no-publications            do not dump publications\n"));
 	printf(_("  --no-security-labels         do not dump security label assignments\n"));
 	printf(_("  --no-subscriptions           do not dump subscriptions\n"));
+	printf(_("  --no-sequence-access-method  do not 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"));
@@ -13103,6 +13107,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;
@@ -17382,26 +17389,40 @@ collectSequences(Archive *fout)
 	 *
 	 * Since version 18, we can gather the sequence data in this query with
 	 * pg_sequence_read_tuple(), 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->schemaOnly && !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->schemaOnly && !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_sequence_read_tuple(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_sequence_read_tuple(s.seqrelid) r "
 			"ORDER BY seqrelid;";
 
 	res = ExecuteSqlQuery(fout, query, PGRES_TUPLES_OK);
@@ -17421,6 +17442,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);
@@ -17492,6 +17517,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);
 	}
@@ -17613,6 +17639,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 882dbf8e86..a35bd3a044 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -97,6 +97,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;
@@ -161,6 +162,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},
@@ -435,6 +437,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)
@@ -668,6 +672,7 @@ help(void)
 	printf(_("  --no-security-labels         do not dump security label assignments\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 df119591cc..081954c5ce 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -68,6 +68,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;
@@ -115,6 +116,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},
@@ -363,6 +365,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;
@@ -491,6 +494,7 @@ usage(const char *progname)
 	printf(_("  --no-publications            do not restore publications\n"));
 	printf(_("  --no-security-labels         do not restore security labels\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(_("  --section=SECTION            restore named section (pre-data, data, or post-data)\n"));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 5bcc2244d5..a6128f7ce2 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -537,6 +537,13 @@ 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', 'postgres',
+		],
+	},
 	no_table_access_method => {
 		dump_cmd => [
 			'pg_dump', '--no-sync',
@@ -711,6 +718,7 @@ my %full_runs = (
 	no_large_objects => 1,
 	no_owner => 1,
 	no_privs => 1,
+	no_sequence_access_method => 1,
 	no_table_access_method => 1,
 	pg_dumpall_dbprivs => 1,
 	pg_dumpall_exclude => 1,
@@ -4488,6 +4496,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
@@ -4516,6 +4536,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 ffc29b04fb..0c392a8080 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1121,6 +1121,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 4d7c046468..34643175fb 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -479,6 +479,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 b8b27e1719..922f69eb4a 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -733,6 +733,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.45.2

v7-0007-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From 2c73ccd10c7c2582bbc3a29a8a0b150db9e15819 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v7 7/8] 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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 2937384b00..38ecfd69c1 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8997,6 +8997,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 a7ff5f8264..07aa3f18c7 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -95,6 +95,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 ec9f90e283..6b82f4c7fc 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -256,6 +256,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 dae43dbaed..3067dc4d4d 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>SEQUENCE</literal> and
+      <literal>INDEX</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 1e283f13d1..52c6096e4b 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 0000000000..a96170bfac
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.45.2

v7-0008-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From 54977af70003644e8b396ea1c5a6f9bb5c74c660 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 26 Apr 2024 15:09:00 +0900
Subject: [PATCH v7 8/8] snowflake: Add sequence AM based on it

This includes documentation and a basic implementation, though this is
in a very early stage.
---
 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            | 570 +++++++++++++++++++++++
 contrib/snowflake/snowflake.control      |   5 +
 contrib/snowflake/sql/snowflake.sql      |  29 ++
 13 files changed, 858 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 44639a8dca..e7b8871e8a 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -166,6 +166,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 07aa3f18c7..496ac19f25 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -156,6 +156,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 0000000000..060699e7ec
--- /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 abd780f277..f5d1b568b2 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -42,6 +42,7 @@ SUBDIRS = \
 		pg_walinspect	\
 		postgres_fdw	\
 		seg		\
+		snowflake	\
 		spi		\
 		tablefunc	\
 		tcn		\
diff --git a/contrib/meson.build b/contrib/meson.build
index 14a8906865..4f2603e7e9 100644
--- a/contrib/meson.build
+++ b/contrib/meson.build
@@ -57,6 +57,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 0000000000..44d119cfcc
--- /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 0000000000..fa5b48d565
--- /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 0000000000..b7e469bf73
--- /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 0000000000..567669eea7
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2024, 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 += bloom
+
+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 0000000000..bcb9d754f1
--- /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 0000000000..be4c4039ec
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,570 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2024, 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/sequenceam.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 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(snowflake_magic));
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+	sm->magic = SNOWFLAKE_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 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, (Item) 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)
+{
+	Page		page;
+	ItemId		lp;
+	snowflake_magic *sm;
+	Form_snowflake_data	seq;
+
+	*buf = ReadBuffer(rel, 0);
+	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(*buf);
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+
+	if (sm->magic != SNOWFLAKE_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_snowflake_data) GETSTRUCT(seqdatatuple);
+
+	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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_snowflake_data seq;
+
+	seq = read_snowflake(rel, &buf, &seqdatatuple);
+	*last_value = seq->count;
+	*is_called = seq->is_called;
+	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 0000000000..7b8c6089c2
--- /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 0000000000..395d166ba4
--- /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.45.2

#25Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#24)
7 attachment(s)
Re: Sequence Access Methods, round two

On Mon, Aug 26, 2024 at 01:45:12PM +0900, Michael Paquier wrote:

Finally, I have been rather annoyed by the addition of log_cnt in the
new function pg_sequence_read_tuple(). This patch set could also
implement a new system function, but it looks like a waste as we don't
care about log_cnt in pg_dump and pg_upgrade on HEAD, so I'm proposing
to remove it on a different thread:
/messages/by-id/Zsvka3r-y2ZoXAdH@paquier.xyz

Following a83a944e9fdd, rebased as v8.
--
Michael

Attachments:

v8-0005-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From fdaf5dab2b0d7f9ed0b0f2d0507f16cc7387a61f Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Aug 2024 13:25:19 +0900
Subject: [PATCH v8 5/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     | 49 +++++++++++++++++++++
 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, 207 insertions(+), 11 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index fbf5f1c515..4eae73c16e 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -95,6 +95,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
@@ -185,6 +186,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 8c20c263c4..0eb5f9de3e 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -174,6 +174,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;
@@ -1224,6 +1225,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;
@@ -2379,6 +2381,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 */
 
@@ -2608,6 +2611,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);
@@ -2712,6 +2716,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);
 
@@ -3367,6 +3374,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;
 
@@ -3529,6 +3539,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.
  */
@@ -3738,6 +3799,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
 	_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);
 
@@ -4262,6 +4324,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);
@@ -5001,6 +5065,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -5060,6 +5125,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 ce5ed1dd39..cdfebcd340 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)
 
@@ -322,6 +323,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 */
 
@@ -353,6 +355,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;
@@ -394,6 +397,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 f7720ad53b..76d71c4f4b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -134,6 +134,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
@@ -477,6 +478,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},
@@ -1098,6 +1100,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;
@@ -1214,6 +1217,7 @@ help(const char *progname)
 	printf(_("  --no-publications            do not dump publications\n"));
 	printf(_("  --no-security-labels         do not dump security label assignments\n"));
 	printf(_("  --no-subscriptions           do not dump subscriptions\n"));
+	printf(_("  --no-sequence-access-method  do not 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"));
@@ -13103,6 +13107,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;
@@ -17382,26 +17389,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->schemaOnly && !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->schemaOnly && !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);
@@ -17421,6 +17442,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);
@@ -17492,6 +17517,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);
 	}
@@ -17613,6 +17639,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 e3ad8fb295..865f11702f 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -97,6 +97,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;
@@ -161,6 +162,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},
@@ -435,6 +437,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)
@@ -668,6 +672,7 @@ help(void)
 	printf(_("  --no-security-labels         do not dump security label assignments\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 df119591cc..081954c5ce 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -68,6 +68,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;
@@ -115,6 +116,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},
@@ -363,6 +365,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;
@@ -491,6 +494,7 @@ usage(const char *progname)
 	printf(_("  --no-publications            do not restore publications\n"));
 	printf(_("  --no-security-labels         do not restore security labels\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(_("  --section=SECTION            restore named section (pre-data, data, or post-data)\n"));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 5bcc2244d5..a6128f7ce2 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -537,6 +537,13 @@ 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', 'postgres',
+		],
+	},
 	no_table_access_method => {
 		dump_cmd => [
 			'pg_dump', '--no-sync',
@@ -711,6 +718,7 @@ my %full_runs = (
 	no_large_objects => 1,
 	no_owner => 1,
 	no_privs => 1,
+	no_sequence_access_method => 1,
 	no_table_access_method => 1,
 	pg_dumpall_dbprivs => 1,
 	pg_dumpall_exclude => 1,
@@ -4488,6 +4496,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
@@ -4516,6 +4536,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 ffc29b04fb..0c392a8080 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1121,6 +1121,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 4d7c046468..34643175fb 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -479,6 +479,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 b8b27e1719..922f69eb4a 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -733,6 +733,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.45.2

v8-0006-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From 7de0149b2b69a133ef8762cce67a16ed2defd087 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v8 6/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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 12feac6087..651caa7491 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8993,6 +8993,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 a7ff5f8264..07aa3f18c7 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -95,6 +95,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 ec9f90e283..6b82f4c7fc 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -256,6 +256,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 dae43dbaed..3067dc4d4d 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>SEQUENCE</literal> and
+      <literal>INDEX</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 1e283f13d1..52c6096e4b 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 0000000000..a96170bfac
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.45.2

v8-0007-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From d799414f07c10bb82a252ef2c1148361f5e99ae1 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 26 Apr 2024 15:09:00 +0900
Subject: [PATCH v8 7/7] snowflake: Add sequence AM based on it

This includes documentation and a basic implementation, though this is
in a very early stage.
---
 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            | 570 +++++++++++++++++++++++
 contrib/snowflake/snowflake.control      |   5 +
 contrib/snowflake/sql/snowflake.sql      |  29 ++
 13 files changed, 858 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 44639a8dca..e7b8871e8a 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -166,6 +166,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 07aa3f18c7..496ac19f25 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -156,6 +156,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 0000000000..060699e7ec
--- /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 abd780f277..f5d1b568b2 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -42,6 +42,7 @@ SUBDIRS = \
 		pg_walinspect	\
 		postgres_fdw	\
 		seg		\
+		snowflake	\
 		spi		\
 		tablefunc	\
 		tcn		\
diff --git a/contrib/meson.build b/contrib/meson.build
index 14a8906865..4f2603e7e9 100644
--- a/contrib/meson.build
+++ b/contrib/meson.build
@@ -57,6 +57,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 0000000000..44d119cfcc
--- /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 0000000000..fa5b48d565
--- /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 0000000000..b7e469bf73
--- /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 0000000000..567669eea7
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2024, 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 += bloom
+
+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 0000000000..bcb9d754f1
--- /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 0000000000..be4c4039ec
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,570 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2024, 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/sequenceam.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 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(snowflake_magic));
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+	sm->magic = SNOWFLAKE_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 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, (Item) 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)
+{
+	Page		page;
+	ItemId		lp;
+	snowflake_magic *sm;
+	Form_snowflake_data	seq;
+
+	*buf = ReadBuffer(rel, 0);
+	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(*buf);
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+
+	if (sm->magic != SNOWFLAKE_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_snowflake_data) GETSTRUCT(seqdatatuple);
+
+	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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_snowflake_data seq;
+
+	seq = read_snowflake(rel, &buf, &seqdatatuple);
+	*last_value = seq->count;
+	*is_called = seq->is_called;
+	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 0000000000..7b8c6089c2
--- /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 0000000000..395d166ba4
--- /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.45.2

v8-0002-Integrate-addition-of-attributes-for-sequences-wi.patchtext/x-diff; charset=us-asciiDownload
From e38c73be51490212cdb44eb0ac5e7d9f598adbd9 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v8 2/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               | 29 +++++++++++++++++--
 src/backend/commands/tablecmds.c              | 10 +++++++
 src/backend/tcop/utility.c                    |  4 +++
 .../test_ddl_deparse/expected/alter_table.out | 10 +++++--
 .../expected/create_sequence_1.out            |  5 +++-
 .../expected/create_table.out                 | 15 ++++++++--
 .../test_ddl_deparse/test_ddl_deparse.c       |  3 ++
 8 files changed, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 124d853e49..f5fe0d2257 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2338,6 +2338,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 e7a31418d2..633af9d40f 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -136,6 +136,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;
@@ -174,7 +177,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;
@@ -198,7 +200,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;
@@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 b3cc6f8f69..700ac3679a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4480,6 +4480,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 */
@@ -4783,6 +4784,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,
@@ -5178,6 +5186,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,
@@ -6339,6 +6348,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 b2ea8125c9..6a65db0910 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1664,7 +1664,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 6daa186a84..24b2179455 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:  DDL test: type simple, tag ALTER SEQUENCE
@@ -76,7 +79,10 @@ NOTICE:    subcommand: type SET NOT NULL desc column a of table parent
 NOTICE:    subcommand: type SET NOT NULL desc column a of table child
 NOTICE:    subcommand: type SET NOT NULL desc column a of table grandchild
 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_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
index 5837ea484e..310ce5a6ba 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.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 2178ce83e9..2be95f99f8 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 2758ae82d7..280642e81c 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -114,6 +114,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.45.2

v8-0003-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From 29154ed1ef980ba152251f5b22cdbfa7a15c77ca Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 25 Apr 2024 16:49:47 +0900
Subject: [PATCH v8 3/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.
- seqlocalxlog.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/seqlocalam.h               |  58 ++
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 .../rmgrdesc/{seqdesc.c => seqlocaldesc.c}    |  20 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/meson.build       |   2 +
 src/backend/access/sequence/seqlocalam.c      | 634 ++++++++++++++++++
 src/backend/access/sequence/seqlocalxlog.c    |  82 +++
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 634 +-----------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 14 files changed, 822 insertions(+), 622 deletions(-)
 create mode 100644 src/include/access/seqlocalam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%)
 create mode 100644 src/backend/access/sequence/seqlocalam.c
 create mode 100644 src/backend/access/sequence/seqlocalxlog.c

diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 78e6b908c6..fa94beb6ed 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/seqlocalam.h b/src/include/access/seqlocalam.h
new file mode 100644
index 0000000000..225fb9a2cb
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2024, 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 "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_SEQ_LOCAL_LOG			0x00
+
+typedef struct xl_seq_local_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_seq_local_rec;
+
+/*
+ * 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;
+
+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);
+
+/* 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);
+extern void seq_local_change_persistence(Relation rel,
+										 char newrelpersistence);
+
+#endif							/* SEQLOCALAM_H */
diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile
index cd95eec37f..e5900ed77a 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 e8b7a65fc7..e390d385bf 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 cf0e02ded5..409c67335e 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.c
  *
  * Portions Copyright (c) 1996-2024, 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.h"
+#include "access/seqlocalam.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 9f9d31f542..a15ceec1c0 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 1843aed38d..4d24f900b6 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2024, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'seqlocalam.c',
+  'seqlocalxlog.c',
   'sequence.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
new file mode 100644
index 0000000000..b0a0d72966
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -0,0 +1,634 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.c
+ *	  Local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, 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/multixact.h"
+#include "access/seqlocalam.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"
+
+
+/*
+ * 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
+
+/* 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, (Item) 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((char *) &xlrec, sizeof(xl_seq_local_rec));
+		XLogRegisterData((char *) 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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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((char *) &xlrec, sizeof(xl_seq_local_rec));
+		XLogRegisterData((char *) 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 "heap";
+}
+
+/*
+ * 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((char *) &xlrec, sizeof(xl_seq_local_rec));
+		XLogRegisterData((char *) 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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_seq_local_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	*last_value = seq->last_value;
+	*is_called = seq->is_called;
+
+	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/sequence/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c
new file mode 100644
index 0000000000..db0ad969db
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalxlog.c
@@ -0,0 +1,82 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalxlog.c
+ *	  WAL replay logic for local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/access/sequence/seqlocalxlog.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/seqlocalam.h"
+#include "access/xlogutils.h"
+#include "storage/block.h"
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+seq_local_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+seq_local_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record);
+	seq_local_magic *sm;
+
+	if (info != XLOG_SEQ_LOCAL_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = (Page) BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	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_local_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec);
+
+	if (PageAddItem(localpage, (Item) item, itemsz,
+					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "seq_local_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 1b7499726e..cc92268937 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 633af9d40f..4443e569a7 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -50,23 +51,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -96,13 +80,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,
@@ -134,14 +114,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
@@ -174,35 +148,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;
@@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	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)
@@ -292,10 +222,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;
@@ -306,7 +232,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))
@@ -315,40 +240,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 */
@@ -357,106 +250,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, (Item) 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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) tuple->t_data, tuple->t_len);
-
-		recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	UnlockReleaseBuffer(buf);
-}
-
 /*
  * AlterSequence
  *
@@ -468,10 +261,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;
@@ -480,7 +270,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	bool		reset_state = false;
 	bool		is_called;
 	int64		last_value;
-	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
 	relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -507,16 +296,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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -526,32 +307,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 */
@@ -580,8 +339,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
@@ -596,10 +353,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);
 }
@@ -662,24 +416,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);
@@ -724,105 +469,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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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;
@@ -832,69 +481,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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) 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;
@@ -984,9 +570,6 @@ do_setval(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,
@@ -1020,9 +603,6 @@ do_setval(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),
@@ -1044,37 +624,8 @@ do_setval(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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) 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);
 }
@@ -1215,62 +766,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
@@ -1852,16 +1347,13 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		Buffer		buf;
-		HeapTupleData seqtuple;
-		Form_pg_sequence_data seq;
+		bool		is_called;
+		int64		last_value;
 
-		seq = read_seq_tuple(seqrel, &buf, &seqtuple);
+		seq_local_get_state(seqrel, &last_value, &is_called);
 
-		values[0] = Int64GetDatum(seq->last_value);
-		values[1] = BoolGetDatum(seq->is_called);
-
-		UnlockReleaseBuffer(buf);
+		values[0] = Int64GetDatum(last_value);
+		values[1] = BoolGetDatum(is_called);
 	}
 	else
 		memset(isnull, true, sizeof(isnull));
@@ -1905,17 +1397,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);
 	}
+
 	sequence_close(seqrel, NoLock);
 
 	if (is_called)
@@ -1924,57 +1408,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
-
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = (Page) BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, (Item) item, itemsz,
-					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1989,14 +1422,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c76..8d1195de26 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/seqlocaldesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index 6b8c17bb4c..2fccc7a4e2 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index 578e473139..0b911048e1 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.45.2

v8-0004-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From 8f16b5140d8b95c11823b8f7f8cf1120d4eca2d4 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 25 Apr 2024 17:15:20 +0900
Subject: [PATCH v8 4/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               |  15 --
 src/include/access/sequenceam.h               | 181 ++++++++++++++++++
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/seqlocalam.c      |  41 +++-
 src/backend/access/sequence/sequence.c        |   3 +-
 src/backend/access/sequence/sequenceamapi.c   | 145 ++++++++++++++
 src/backend/catalog/heap.c                    |   6 +-
 src/backend/commands/amcmds.c                 |  16 ++
 src/backend/commands/sequence.c               |  23 +--
 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_tables.c           |  12 ++
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/bin/psql/describe.c                       |   2 +
 src/bin/psql/tab-complete.c                   |   4 +-
 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/sql/create_am.sql            |  24 ++-
 src/test/regress/sql/opr_sanity.sql           |  10 +
 src/tools/pgindent/typedefs.list              |   5 +-
 38 files changed, 683 insertions(+), 170 deletions(-)
 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
index 225fb9a2cb..21936511ac 100644
--- a/src/include/access/seqlocalam.h
+++ b/src/include/access/seqlocalam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_SEQ_LOCAL_LOG			0x00
@@ -41,18 +40,4 @@ 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);
 
-/* 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);
-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 0000000000..ac48c8b468
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,181 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2024, 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.
+	 */
+	void		(*get_state) (Relation rel, int64 *last_value, bool *is_called);
+
+	/*
+	 * 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)
+{
+	rel->rd_sequenceam->get_state(rel, last_value, is_called);
+}
+
+/*
+ * 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 db87490282..d991a77604 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 => '8048', 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 475593fad4..3a94e8c636 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 0fc2c093b0..21623f34fa 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -228,6 +228,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)
+
 extern int	errdetail_relkind_not_supported(char relkind);
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 85f42be1b3..039ec73a24 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',
@@ -7677,6 +7683,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 ceff66ccde..0eba013687 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -627,6 +627,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 29c511e319..39d2cae422 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -141,6 +141,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 e88cbee3b5..9b22b2f248 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index b665e55b65..774ae2652c 100644
--- a/src/include/nodes/meson.build
+++ b/src/include/nodes/meson.build
@@ -9,6 +9,7 @@ node_support_input_i = [
   '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/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f5fe0d2257..6dc56e3eaf 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3112,6 +3112,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 5813dba0a2..f4a49969b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -53,6 +53,8 @@ extern bool check_debug_io_direct(char **newval, void **extra, GucSource source)
 extern void assign_debug_io_direct(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 8700204953..2a4b7bed2b 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 a15ceec1c0..62006165a1 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = seqlocalam.o seqlocalxlog.o sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 4d24f900b6..ea91990552 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -4,4 +4,5 @@ backend_sources += files(
   'seqlocalam.c',
   'seqlocalxlog.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
index b0a0d72966..2e524c9e99 100644
--- a/src/backend/access/sequence/seqlocalam.c
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -17,6 +17,7 @@
 
 #include "access/multixact.h"
 #include "access/seqlocalam.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -24,6 +25,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -230,10 +232,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;
@@ -417,7 +419,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 "heap";
@@ -432,7 +434,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];
@@ -499,7 +501,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;
@@ -547,7 +549,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;
@@ -600,7 +602,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)
 {
 	Buffer		buf;
@@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called)
  *
  * Persistence change for the local sequence Relation.
  */
-void
+static void
 seq_local_change_persistence(Relation rel, char newrelpersistence)
 {
 	Buffer		buf;
@@ -632,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 8d6b7bb5dc..5e7d76d3b0 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 0000000000..dd1a60d827
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2024, 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/xact.h"
+#include "access/sequenceam.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 01b43cc6a8..3b8d5a9989 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1472,8 +1472,12 @@ 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.
+		 *
+		 * 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)
+		if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE) ||
+			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 aaa0f9a1dc..1a27f191a3 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 4443e569a7..42c9c26aa0 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -152,6 +152,7 @@ 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;
@@ -173,7 +174,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)
@@ -241,7 +242,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 */
@@ -297,7 +298,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
 
 	/* Read sequence data */
-	seq_local_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -310,7 +311,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 */
@@ -353,7 +354,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);
 }
@@ -470,8 +471,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;
@@ -625,7 +626,7 @@ do_setval(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);
 }
@@ -1350,7 +1351,7 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		bool		is_called;
 		int64		last_value;
 
-		seq_local_get_state(seqrel, &last_value, &is_called);
+		sequence_get_state(seqrel, &last_value, &is_called);
 
 		values[0] = Int64GetDatum(last_value);
 		values[1] = BoolGetDatum(is_called);
@@ -1397,7 +1398,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		seq_local_get_state(seqrel, &result, &is_called);
+		sequence_get_state(seqrel, &result, &is_called);
 	}
 
 	sequence_close(seqrel, NoLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 700ac3679a..795b06b5ea 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -22,6 +22,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"
@@ -943,14 +944,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)
 	{
@@ -963,6 +968,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 constraints are passed in
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 66bbad8e6e..86b1773c0b 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -47,6 +47,7 @@ node_headers = \
 	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/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 81df3bdf95..d2450e559d 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -59,6 +59,7 @@ my @all_input_files = qw(
   nodes/execnodes.h
   access/amapi.h
   access/sdir.h
+  access/sequenceam.h
   access/tableam.h
   access/tsmapi.h
   commands/event_trigger.h
@@ -83,6 +84,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 84cef57a70..8e376d974a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -390,6 +390,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
@@ -4831,23 +4832,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;
@@ -4884,6 +4888,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); }
 		;
@@ -5880,6 +5889,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 79cad4ab30..e2fd33a530 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"
@@ -461,6 +462,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	seqstmt->sequence->relpersistence = cxt->rel ? cxt->rel->rd_rel->relpersistence : cxt->relation->relpersistence;
 
 	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 e189e9b79d..e31dbb0ebb 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 66ed24e401..6f80d4f707 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"
@@ -301,6 +303,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);
@@ -1204,8 +1207,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)
 	{
@@ -1214,6 +1216,8 @@ retry:
 		 * inherit.
 		 */
 	}
+	else if (RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind))
+		RelationInitSequenceAccessMethod(relation);
 	else
 		Assert(relation->rd_rel->relam == InvalidOid);
 
@@ -1810,17 +1814,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.
@@ -1851,6 +1847,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
  *
@@ -3711,14 +3750,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);
 
 	/*
 	 * Okay to insert into the relcache hash table.
@@ -4333,13 +4375,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);
@@ -6326,8 +6376,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);
@@ -6339,6 +6391,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_tables.c b/src/backend/utils/misc/guc_tables.c
index 880d76aae0..8534867f43 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -30,6 +30,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"
@@ -4185,6 +4186,17 @@ struct config_string ConfigureNamesString[] =
 		check_default_table_access_method, NULL, NULL
 	},
 
+	{
+		{"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets the default sequence access method for new sequences."),
+			NULL,
+			GUC_IS_NAME
+		},
+		&default_sequence_access_method,
+		DEFAULT_SEQUENCE_ACCESS_METHOD,
+		check_default_sequence_access_method, NULL, NULL
+	},
+
 	{
 		{"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default tablespace to create tables and indexes in."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 667e0dc40a..879c03c770 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -703,6 +703,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 7c9a1f234c..98ff40f00d 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -161,10 +161,12 @@ describeAccessMethods(const char *pattern, bool verbose)
 					  "SELECT amname AS \"%s\",\n"
 					  "  CASE amtype"
 					  " WHEN 'i' THEN '%s'"
+					  " WHEN 's' THEN '%s'"
 					  " WHEN 't' 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.c b/src/bin/psql/tab-complete.c
index a7ccde6d7d..0352c73e30 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2200,7 +2200,7 @@ psql_completion(const char *text, int start, int end)
 	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");
@@ -3226,7 +3226,7 @@ psql_completion(const char *text, int start, int end)
 	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 35d4cf1d46..f1ad1c583f 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;
@@ -500,9 +495,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;
@@ -519,18 +517,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
@@ -560,3 +558,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 0d734169f1..ea1f1048ea 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1933,6 +1933,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 6aeb7cb963..beb01d78a4 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5017,31 +5017,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
@@ -5066,32 +5068,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/sql/create_am.sql b/src/test/regress/sql/create_am.sql
index 825aed325e..472289994e 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;
@@ -317,9 +314,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;
@@ -355,3 +356,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 2fe7b6dcc4..1409622374 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1229,6 +1229,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/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 9e951a9e6f..6e1911a76d 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2579,6 +2579,7 @@ SeqScanState
 SeqTable
 SeqTableData
 SeqType
+SequenceAmRoutine
 SequenceItem
 SerCommitSeqNo
 SerialControl
@@ -3596,6 +3597,7 @@ lineno_t
 list_sort_comparator
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 local_ts_iter
 local_ts_radix_tree
@@ -3878,7 +3880,6 @@ scram_state
 scram_state_enum
 security_class_t
 sem_t
-sepgsql_context_info_t
 sequence_magic
 set_join_pathlist_hook_type
 set_rel_pathlist_hook_type
@@ -4100,6 +4101,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
@@ -4111,7 +4113,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.45.2

v8-0001-Remove-FormData_pg_sequence_data-from-init_params.patchtext/x-diff; charset=us-asciiDownload
From 95022429328d5d9eb6d80358d19cd8b0b0b0da5a Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:00:45 +0900
Subject: [PATCH v8 1/7] Remove FormData_pg_sequence_data from
 init_params()/sequence.c

init_params() sets up "last_value" and "is_called" for a sequence, based
on the sequence properties in pg_sequences.  This simplifies the logic
around log_cnt, which is reset to 0 when the metadata of a sequence is
expected to start from afresh when its properties are updated.
---
 src/backend/commands/sequence.c | 81 ++++++++++++++++++++-------------
 1 file changed, 49 insertions(+), 32 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index b37fd688d3..e7a31418d2 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -106,7 +106,9 @@ static Form_pg_sequence_data read_seq_tuple(Relation rel,
 static void init_params(ParseState *pstate, List *options, bool for_identity,
 						bool isInit,
 						Form_pg_sequence seqform,
-						Form_pg_sequence_data seqdataform,
+						int64 *last_value,
+						bool *reset_state,
+						bool *is_called,
 						bool *need_seq_rewrite,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
@@ -121,7 +123,9 @@ ObjectAddress
 DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 {
 	FormData_pg_sequence seqform;
-	FormData_pg_sequence_data seqdataform;
+	int64		last_value;
+	bool		reset_state;
+	bool		is_called;
 	bool		need_seq_rewrite;
 	List	   *owned_by;
 	CreateStmt *stmt = makeNode(CreateStmt);
@@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* Check and set all option values */
 	init_params(pstate, seq->options, seq->for_identity, true,
-				&seqform, &seqdataform,
+				&seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/*
@@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 		{
 			case SEQ_COL_LASTVAL:
 				coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
-				value[i - 1] = Int64GetDatumFast(seqdataform.last_value);
+				value[i - 1] = Int64GetDatumFast(last_value);
 				break;
 			case SEQ_COL_LOG:
 				coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
@@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	ObjectAddress address;
 	Relation	rel;
 	HeapTuple	seqtuple;
+	bool		reset_state = false;
+	bool		is_called;
+	int64		last_value;
 	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
@@ -481,12 +488,14 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	/* 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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
-				seqform, newdataform,
+				seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/* If needed, rewrite the sequence relation itself */
@@ -513,6 +522,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * 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);
 	}
 
@@ -1236,17 +1249,19 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 /*
  * init_params: process the options list of CREATE or ALTER SEQUENCE, and
  * store the values into appropriate fields of seqform, for changes that go
- * into the pg_sequence catalog, and fields of seqdataform for changes to the
- * sequence relation itself.  Set *need_seq_rewrite to true if we changed any
- * parameters that require rewriting the sequence's relation (interesting for
- * ALTER SEQUENCE).  Also set *owned_by to any OWNED BY option, or to NIL if
- * there is none.
+ * into the pg_sequence catalog, and fields for changes to the sequence
+ * relation itself (is_called, last_value or any state it may hold).  Set
+ * *need_seq_rewrite to true if we changed any parameters that require
+ * rewriting the sequence's relation (interesting for ALTER SEQUENCE).  Also
+ * set *owned_by to any OWNED BY option, or to NIL if there is none.  Set
+ * *reset_state if the internal state of the sequence needs to change on a
+ * follow-up nextval().
  *
  * If isInit is true, fill any unspecified options with default values;
  * otherwise, do not change existing options that aren't explicitly overridden.
  *
  * Note: we force a sequence rewrite whenever we change parameters that affect
- * generation of future sequence values, even if the seqdataform per se is not
+ * generation of future sequence values, even if the metadata per se is not
  * changed.  This allows ALTER SEQUENCE to behave transactionally.  Currently,
  * the only option that doesn't cause that is OWNED BY.  It's *necessary* for
  * ALTER SEQUENCE OWNED BY to not rewrite the sequence, because that would
@@ -1257,7 +1272,9 @@ static void
 init_params(ParseState *pstate, List *options, bool for_identity,
 			bool isInit,
 			Form_pg_sequence seqform,
-			Form_pg_sequence_data seqdataform,
+			int64 *last_value,
+			bool *reset_state,
+			bool *is_called,
 			bool *need_seq_rewrite,
 			List **owned_by)
 {
@@ -1360,11 +1377,11 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	}
 
 	/*
-	 * We must reset log_cnt when isInit or when changing any parameters that
-	 * would affect future nextval allocations.
+	 * We must reset the state when isInit or when changing any parameters
+	 * that would affect future nextval allocations.
 	 */
 	if (isInit)
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 
 	/* AS type */
 	if (as_type != NULL)
@@ -1413,7 +1430,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("INCREMENT must not be zero")));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1425,7 +1442,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	{
 		seqform->seqcycle = boolVal(is_cycled->arg);
 		Assert(BoolIsValid(seqform->seqcycle));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1436,7 +1453,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (max_value != NULL && max_value->arg)
 	{
 		seqform->seqmax = defGetInt64(max_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || max_value != NULL || reset_max_value)
 	{
@@ -1452,7 +1469,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmax = -1;	/* descending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate maximum value.  No need to check INT8 as seqmax is an int64 */
@@ -1468,7 +1485,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (min_value != NULL && min_value->arg)
 	{
 		seqform->seqmin = defGetInt64(min_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || min_value != NULL || reset_min_value)
 	{
@@ -1484,7 +1501,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmin = 1;	/* ascending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate minimum value.  No need to check INT8 as seqmin is an int64 */
@@ -1535,30 +1552,30 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (restart_value != NULL)
 	{
 		if (restart_value->arg != NULL)
-			seqdataform->last_value = defGetInt64(restart_value);
+			*last_value = defGetInt64(restart_value);
 		else
-			seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
-		seqdataform->log_cnt = 0;
+			*last_value = seqform->seqstart;
+		*is_called = false;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-		seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
+		*last_value = seqform->seqstart;
+		*is_called = false;
 	}
 
 	/* crosscheck RESTART (or current value, if changing MIN/MAX) */
-	if (seqdataform->last_value < seqform->seqmin)
+	if (*last_value < seqform->seqmin)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be less than MINVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmin)));
-	if (seqdataform->last_value > seqform->seqmax)
+	if (*last_value > seqform->seqmax)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be greater than MAXVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmax)));
 
 	/* CACHE */
@@ -1570,7 +1587,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("CACHE (%lld) must be greater than zero",
 							(long long) seqform->seqcache)));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-- 
2.45.2

#26Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#25)
7 attachment(s)
Re: Sequence Access Methods, round two

On Fri, Aug 30, 2024 at 05:24:19PM +0900, Michael Paquier wrote:

Following a83a944e9fdd, rebased as v8.

Rebased as v29 due to an OID conflict.
--
Michael

Attachments:

v9-0001-Remove-FormData_pg_sequence_data-from-init_params.patchtext/x-diff; charset=us-asciiDownload
From 4f5853a61e20d49f641f312bfb62d183a7948f3b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:00:45 +0900
Subject: [PATCH v9 1/7] Remove FormData_pg_sequence_data from
 init_params()/sequence.c

init_params() sets up "last_value" and "is_called" for a sequence, based
on the sequence properties in pg_sequences.  This simplifies the logic
around log_cnt, which is reset to 0 when the metadata of a sequence is
expected to start from afresh when its properties are updated.
---
 src/backend/commands/sequence.c | 81 ++++++++++++++++++++-------------
 1 file changed, 49 insertions(+), 32 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0188e8bbd5..ed2cd2b1fc 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -106,7 +106,9 @@ static Form_pg_sequence_data read_seq_tuple(Relation rel,
 static void init_params(ParseState *pstate, List *options, bool for_identity,
 						bool isInit,
 						Form_pg_sequence seqform,
-						Form_pg_sequence_data seqdataform,
+						int64 *last_value,
+						bool *reset_state,
+						bool *is_called,
 						bool *need_seq_rewrite,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
@@ -121,7 +123,9 @@ ObjectAddress
 DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 {
 	FormData_pg_sequence seqform;
-	FormData_pg_sequence_data seqdataform;
+	int64		last_value;
+	bool		reset_state;
+	bool		is_called;
 	bool		need_seq_rewrite;
 	List	   *owned_by;
 	CreateStmt *stmt = makeNode(CreateStmt);
@@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* Check and set all option values */
 	init_params(pstate, seq->options, seq->for_identity, true,
-				&seqform, &seqdataform,
+				&seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/*
@@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 		{
 			case SEQ_COL_LASTVAL:
 				coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
-				value[i - 1] = Int64GetDatumFast(seqdataform.last_value);
+				value[i - 1] = Int64GetDatumFast(last_value);
 				break;
 			case SEQ_COL_LOG:
 				coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
@@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	ObjectAddress address;
 	Relation	rel;
 	HeapTuple	seqtuple;
+	bool		reset_state = false;
+	bool		is_called;
+	int64		last_value;
 	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
@@ -481,12 +488,14 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	/* 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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
-				seqform, newdataform,
+				seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/* If needed, rewrite the sequence relation itself */
@@ -513,6 +522,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * 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);
 	}
 
@@ -1236,17 +1249,19 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 /*
  * init_params: process the options list of CREATE or ALTER SEQUENCE, and
  * store the values into appropriate fields of seqform, for changes that go
- * into the pg_sequence catalog, and fields of seqdataform for changes to the
- * sequence relation itself.  Set *need_seq_rewrite to true if we changed any
- * parameters that require rewriting the sequence's relation (interesting for
- * ALTER SEQUENCE).  Also set *owned_by to any OWNED BY option, or to NIL if
- * there is none.
+ * into the pg_sequence catalog, and fields for changes to the sequence
+ * relation itself (is_called, last_value or any state it may hold).  Set
+ * *need_seq_rewrite to true if we changed any parameters that require
+ * rewriting the sequence's relation (interesting for ALTER SEQUENCE).  Also
+ * set *owned_by to any OWNED BY option, or to NIL if there is none.  Set
+ * *reset_state if the internal state of the sequence needs to change on a
+ * follow-up nextval().
  *
  * If isInit is true, fill any unspecified options with default values;
  * otherwise, do not change existing options that aren't explicitly overridden.
  *
  * Note: we force a sequence rewrite whenever we change parameters that affect
- * generation of future sequence values, even if the seqdataform per se is not
+ * generation of future sequence values, even if the metadata per se is not
  * changed.  This allows ALTER SEQUENCE to behave transactionally.  Currently,
  * the only option that doesn't cause that is OWNED BY.  It's *necessary* for
  * ALTER SEQUENCE OWNED BY to not rewrite the sequence, because that would
@@ -1257,7 +1272,9 @@ static void
 init_params(ParseState *pstate, List *options, bool for_identity,
 			bool isInit,
 			Form_pg_sequence seqform,
-			Form_pg_sequence_data seqdataform,
+			int64 *last_value,
+			bool *reset_state,
+			bool *is_called,
 			bool *need_seq_rewrite,
 			List **owned_by)
 {
@@ -1363,11 +1380,11 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	}
 
 	/*
-	 * We must reset log_cnt when isInit or when changing any parameters that
-	 * would affect future nextval allocations.
+	 * We must reset the state when isInit or when changing any parameters
+	 * that would affect future nextval allocations.
 	 */
 	if (isInit)
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 
 	/* AS type */
 	if (as_type != NULL)
@@ -1416,7 +1433,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("INCREMENT must not be zero")));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1428,7 +1445,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	{
 		seqform->seqcycle = boolVal(is_cycled->arg);
 		Assert(BoolIsValid(seqform->seqcycle));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1439,7 +1456,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (max_value != NULL && max_value->arg)
 	{
 		seqform->seqmax = defGetInt64(max_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || max_value != NULL || reset_max_value)
 	{
@@ -1455,7 +1472,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmax = -1;	/* descending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate maximum value.  No need to check INT8 as seqmax is an int64 */
@@ -1471,7 +1488,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (min_value != NULL && min_value->arg)
 	{
 		seqform->seqmin = defGetInt64(min_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || min_value != NULL || reset_min_value)
 	{
@@ -1487,7 +1504,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmin = 1;	/* ascending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate minimum value.  No need to check INT8 as seqmin is an int64 */
@@ -1538,30 +1555,30 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (restart_value != NULL)
 	{
 		if (restart_value->arg != NULL)
-			seqdataform->last_value = defGetInt64(restart_value);
+			*last_value = defGetInt64(restart_value);
 		else
-			seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
-		seqdataform->log_cnt = 0;
+			*last_value = seqform->seqstart;
+		*is_called = false;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-		seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
+		*last_value = seqform->seqstart;
+		*is_called = false;
 	}
 
 	/* crosscheck RESTART (or current value, if changing MIN/MAX) */
-	if (seqdataform->last_value < seqform->seqmin)
+	if (*last_value < seqform->seqmin)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be less than MINVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmin)));
-	if (seqdataform->last_value > seqform->seqmax)
+	if (*last_value > seqform->seqmax)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be greater than MAXVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmax)));
 
 	/* CACHE */
@@ -1573,7 +1590,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("CACHE (%lld) must be greater than zero",
 							(long long) seqform->seqcache)));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-- 
2.45.2

v9-0002-Integrate-addition-of-attributes-for-sequences-wi.patchtext/x-diff; charset=us-asciiDownload
From d1527477226df9df7d638e032aa331181e4fcc6d Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v9 2/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               | 29 +++++++++++++++++--
 src/backend/commands/tablecmds.c              | 10 +++++++
 src/backend/tcop/utility.c                    |  4 +++
 .../test_ddl_deparse/expected/alter_table.out | 10 +++++--
 .../expected/create_sequence_1.out            |  5 +++-
 .../expected/create_table.out                 | 15 ++++++++--
 .../test_ddl_deparse/test_ddl_deparse.c       |  3 ++
 8 files changed, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1c314cd907..a623cb1612 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2347,6 +2347,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 ed2cd2b1fc..158ea79c66 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -136,6 +136,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;
@@ -174,7 +177,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;
@@ -198,7 +200,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;
@@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 96373323b8..ea4c2d0d83 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4492,6 +4492,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 */
@@ -4796,6 +4797,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,
@@ -5229,6 +5237,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,
@@ -6391,6 +6400,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 b2ea8125c9..6a65db0910 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1664,7 +1664,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 6daa186a84..24b2179455 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:  DDL test: type simple, tag ALTER SEQUENCE
@@ -76,7 +79,10 @@ NOTICE:    subcommand: type SET NOT NULL desc column a of table parent
 NOTICE:    subcommand: type SET NOT NULL desc column a of table child
 NOTICE:    subcommand: type SET NOT NULL desc column a of table grandchild
 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_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
index 5837ea484e..310ce5a6ba 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.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 2178ce83e9..2be95f99f8 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 2758ae82d7..280642e81c 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -114,6 +114,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.45.2

v9-0003-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From fd9ddc51d7a16097633d07902d27da7924b29c08 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 25 Apr 2024 16:49:47 +0900
Subject: [PATCH v9 3/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.
- seqlocalxlog.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/seqlocalam.h               |  58 ++
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 .../rmgrdesc/{seqdesc.c => seqlocaldesc.c}    |  20 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/meson.build       |   2 +
 src/backend/access/sequence/seqlocalam.c      | 634 ++++++++++++++++++
 src/backend/access/sequence/seqlocalxlog.c    |  82 +++
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 634 +-----------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 14 files changed, 822 insertions(+), 622 deletions(-)
 create mode 100644 src/include/access/seqlocalam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%)
 create mode 100644 src/backend/access/sequence/seqlocalam.c
 create mode 100644 src/backend/access/sequence/seqlocalxlog.c

diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 78e6b908c6..fa94beb6ed 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/seqlocalam.h b/src/include/access/seqlocalam.h
new file mode 100644
index 0000000000..225fb9a2cb
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2024, 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 "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_SEQ_LOCAL_LOG			0x00
+
+typedef struct xl_seq_local_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_seq_local_rec;
+
+/*
+ * 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;
+
+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);
+
+/* 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);
+extern void seq_local_change_persistence(Relation rel,
+										 char newrelpersistence);
+
+#endif							/* SEQLOCALAM_H */
diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile
index cd95eec37f..e5900ed77a 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 e8b7a65fc7..e390d385bf 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 cf0e02ded5..409c67335e 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.c
  *
  * Portions Copyright (c) 1996-2024, 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.h"
+#include "access/seqlocalam.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 9f9d31f542..a15ceec1c0 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 1843aed38d..4d24f900b6 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2024, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'seqlocalam.c',
+  'seqlocalxlog.c',
   'sequence.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
new file mode 100644
index 0000000000..b0a0d72966
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -0,0 +1,634 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.c
+ *	  Local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, 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/multixact.h"
+#include "access/seqlocalam.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"
+
+
+/*
+ * 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
+
+/* 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, (Item) 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((char *) &xlrec, sizeof(xl_seq_local_rec));
+		XLogRegisterData((char *) 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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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((char *) &xlrec, sizeof(xl_seq_local_rec));
+		XLogRegisterData((char *) 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 "heap";
+}
+
+/*
+ * 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((char *) &xlrec, sizeof(xl_seq_local_rec));
+		XLogRegisterData((char *) 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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_seq_local_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	*last_value = seq->last_value;
+	*is_called = seq->is_called;
+
+	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/sequence/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c
new file mode 100644
index 0000000000..db0ad969db
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalxlog.c
@@ -0,0 +1,82 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalxlog.c
+ *	  WAL replay logic for local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/access/sequence/seqlocalxlog.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/seqlocalam.h"
+#include "access/xlogutils.h"
+#include "storage/block.h"
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+seq_local_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+seq_local_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record);
+	seq_local_magic *sm;
+
+	if (info != XLOG_SEQ_LOCAL_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = (Page) BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	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_local_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec);
+
+	if (PageAddItem(localpage, (Item) item, itemsz,
+					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "seq_local_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 1b7499726e..cc92268937 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 158ea79c66..1cad06da66 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -50,23 +51,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -96,13 +80,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,
@@ -134,14 +114,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
@@ -174,35 +148,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;
@@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	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)
@@ -292,10 +222,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;
@@ -306,7 +232,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))
@@ -315,40 +240,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 */
@@ -357,106 +250,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, (Item) 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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) tuple->t_data, tuple->t_len);
-
-		recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	UnlockReleaseBuffer(buf);
-}
-
 /*
  * AlterSequence
  *
@@ -468,10 +261,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;
@@ -480,7 +270,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	bool		reset_state = false;
 	bool		is_called;
 	int64		last_value;
-	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
 	relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -507,16 +296,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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -526,32 +307,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 */
@@ -580,8 +339,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
@@ -596,10 +353,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);
 }
@@ -662,24 +416,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);
@@ -724,105 +469,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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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;
@@ -832,69 +481,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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) 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;
@@ -984,9 +570,6 @@ do_setval(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,
@@ -1020,9 +603,6 @@ do_setval(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),
@@ -1044,37 +624,8 @@ do_setval(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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) 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);
 }
@@ -1215,62 +766,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
@@ -1855,16 +1350,13 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		Buffer		buf;
-		HeapTupleData seqtuple;
-		Form_pg_sequence_data seq;
+		bool		is_called;
+		int64		last_value;
 
-		seq = read_seq_tuple(seqrel, &buf, &seqtuple);
+		seq_local_get_state(seqrel, &last_value, &is_called);
 
-		values[0] = Int64GetDatum(seq->last_value);
-		values[1] = BoolGetDatum(seq->is_called);
-
-		UnlockReleaseBuffer(buf);
+		values[0] = Int64GetDatum(last_value);
+		values[1] = BoolGetDatum(is_called);
 	}
 	else
 		memset(isnull, true, sizeof(isnull));
@@ -1908,17 +1400,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);
 	}
+
 	sequence_close(seqrel, NoLock);
 
 	if (is_called)
@@ -1927,57 +1411,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
-
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = (Page) BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, (Item) item, itemsz,
-					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1992,14 +1425,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c76..8d1195de26 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/seqlocaldesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index 6b8c17bb4c..2fccc7a4e2 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index 578e473139..0b911048e1 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.45.2

v9-0004-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From 1333a9f6fe1253243abb81dc904dd283d8e82561 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 25 Apr 2024 17:15:20 +0900
Subject: [PATCH v9 4/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               |  15 --
 src/include/access/sequenceam.h               | 181 ++++++++++++++++++
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/seqlocalam.c      |  41 +++-
 src/backend/access/sequence/sequence.c        |   3 +-
 src/backend/access/sequence/sequenceamapi.c   | 145 ++++++++++++++
 src/backend/catalog/heap.c                    |   6 +-
 src/backend/commands/amcmds.c                 |  16 ++
 src/backend/commands/sequence.c               |  23 +--
 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_tables.c           |  12 ++
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/bin/psql/describe.c                       |   2 +
 src/bin/psql/tab-complete.c                   |   4 +-
 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/sql/create_am.sql            |  24 ++-
 src/test/regress/sql/opr_sanity.sql           |  10 +
 src/tools/pgindent/typedefs.list              |   5 +-
 38 files changed, 683 insertions(+), 170 deletions(-)
 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
index 225fb9a2cb..21936511ac 100644
--- a/src/include/access/seqlocalam.h
+++ b/src/include/access/seqlocalam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_SEQ_LOCAL_LOG			0x00
@@ -41,18 +40,4 @@ 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);
 
-/* 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);
-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 0000000000..ac48c8b468
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,181 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2024, 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.
+	 */
+	void		(*get_state) (Relation rel, int64 *last_value, bool *is_called);
+
+	/*
+	 * 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)
+{
+	rel->rd_sequenceam->get_state(rel, last_value, is_called);
+}
+
+/*
+ * 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 db87490282..ba9766fae4 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 475593fad4..3a94e8c636 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 0fc2c093b0..21623f34fa 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -228,6 +228,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)
+
 extern int	errdetail_relkind_not_supported(char relkind);
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 05fcbf7515..f1ab5fd1b7 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',
@@ -7740,6 +7746,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 ceff66ccde..0eba013687 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -627,6 +627,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 5fd095ea17..a43a6cbf85 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -145,6 +145,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 e88cbee3b5..9b22b2f248 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index b665e55b65..774ae2652c 100644
--- a/src/include/nodes/meson.build
+++ b/src/include/nodes/meson.build
@@ -9,6 +9,7 @@ node_support_input_i = [
   '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/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index a623cb1612..99a48777b4 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3139,6 +3139,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 5813dba0a2..f4a49969b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -53,6 +53,8 @@ extern bool check_debug_io_direct(char **newval, void **extra, GucSource source)
 extern void assign_debug_io_direct(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 8700204953..2a4b7bed2b 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 a15ceec1c0..62006165a1 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = seqlocalam.o seqlocalxlog.o sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 4d24f900b6..ea91990552 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -4,4 +4,5 @@ backend_sources += files(
   'seqlocalam.c',
   'seqlocalxlog.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
index b0a0d72966..2e524c9e99 100644
--- a/src/backend/access/sequence/seqlocalam.c
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -17,6 +17,7 @@
 
 #include "access/multixact.h"
 #include "access/seqlocalam.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -24,6 +25,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -230,10 +232,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;
@@ -417,7 +419,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 "heap";
@@ -432,7 +434,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];
@@ -499,7 +501,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;
@@ -547,7 +549,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;
@@ -600,7 +602,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)
 {
 	Buffer		buf;
@@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called)
  *
  * Persistence change for the local sequence Relation.
  */
-void
+static void
 seq_local_change_persistence(Relation rel, char newrelpersistence)
 {
 	Buffer		buf;
@@ -632,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 8d6b7bb5dc..5e7d76d3b0 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 0000000000..dd1a60d827
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2024, 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/xact.h"
+#include "access/sequenceam.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 78e59384d1..a5c1e9df73 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1472,8 +1472,12 @@ 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.
+		 *
+		 * 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)
+		if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE) ||
+			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 aaa0f9a1dc..1a27f191a3 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 1cad06da66..a65e93bbee 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -152,6 +152,7 @@ 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;
@@ -173,7 +174,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)
@@ -241,7 +242,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 */
@@ -297,7 +298,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
 
 	/* Read sequence data */
-	seq_local_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -310,7 +311,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 */
@@ -353,7 +354,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);
 }
@@ -470,8 +471,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;
@@ -625,7 +626,7 @@ do_setval(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);
 }
@@ -1353,7 +1354,7 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		bool		is_called;
 		int64		last_value;
 
-		seq_local_get_state(seqrel, &last_value, &is_called);
+		sequence_get_state(seqrel, &last_value, &is_called);
 
 		values[0] = Int64GetDatum(last_value);
 		values[1] = BoolGetDatum(is_called);
@@ -1400,7 +1401,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		seq_local_get_state(seqrel, &result, &is_called);
+		sequence_get_state(seqrel, &result, &is_called);
 	}
 
 	sequence_close(seqrel, NoLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ea4c2d0d83..65f9336b86 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"
@@ -949,14 +950,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)
 	{
@@ -969,6 +974,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 constraints are passed in
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 66bbad8e6e..86b1773c0b 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -47,6 +47,7 @@ node_headers = \
 	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/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 81df3bdf95..d2450e559d 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -59,6 +59,7 @@ my @all_input_files = qw(
   nodes/execnodes.h
   access/amapi.h
   access/sdir.h
+  access/sequenceam.h
   access/tableam.h
   access/tsmapi.h
   commands/event_trigger.h
@@ -83,6 +84,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 4aa8646af7..835d680c87 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -390,6 +390,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
@@ -4883,23 +4884,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;
@@ -4936,6 +4940,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); }
 		;
@@ -5939,6 +5948,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 1e15ce10b4..88fe3661c4 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"
@@ -489,6 +490,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 e189e9b79d..e31dbb0ebb 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 c326f687eb..a47ed6e52a 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"
@@ -301,6 +303,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);
@@ -1204,8 +1207,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)
 	{
@@ -1214,6 +1216,8 @@ retry:
 		 * inherit.
 		 */
 	}
+	else if (RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind))
+		RelationInitSequenceAccessMethod(relation);
 	else
 		Assert(relation->rd_rel->relam == InvalidOid);
 
@@ -1810,17 +1814,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.
@@ -1851,6 +1847,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
  *
@@ -3711,14 +3750,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);
 
 	/*
 	 * Okay to insert into the relcache hash table.
@@ -4336,13 +4378,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);
@@ -6335,8 +6385,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);
@@ -6348,6 +6400,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_tables.c b/src/backend/utils/misc/guc_tables.c
index 686309db58..75bed693da 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -30,6 +30,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"
@@ -4185,6 +4186,17 @@ struct config_string ConfigureNamesString[] =
 		check_default_table_access_method, NULL, NULL
 	},
 
+	{
+		{"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets the default sequence access method for new sequences."),
+			NULL,
+			GUC_IS_NAME
+		},
+		&default_sequence_access_method,
+		DEFAULT_SEQUENCE_ACCESS_METHOD,
+		check_default_sequence_access_method, NULL, NULL
+	},
+
 	{
 		{"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default tablespace to create tables and indexes in."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 667e0dc40a..879c03c770 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -703,6 +703,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 6a36c91083..449fd3febf 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -161,10 +161,12 @@ describeAccessMethods(const char *pattern, bool verbose)
 					  "SELECT amname AS \"%s\",\n"
 					  "  CASE amtype"
 					  " WHEN 'i' THEN '%s'"
+					  " WHEN 's' THEN '%s'"
 					  " WHEN 't' 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.c b/src/bin/psql/tab-complete.c
index a7ccde6d7d..0352c73e30 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2200,7 +2200,7 @@ psql_completion(const char *text, int start, int end)
 	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");
@@ -3226,7 +3226,7 @@ psql_completion(const char *text, int start, int end)
 	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 35d4cf1d46..f1ad1c583f 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;
@@ -500,9 +495,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;
@@ -519,18 +517,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
@@ -560,3 +558,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 0d734169f1..ea1f1048ea 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1933,6 +1933,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 3819bf5e25..f5661cf886 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5064,31 +5064,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
@@ -5113,32 +5115,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/sql/create_am.sql b/src/test/regress/sql/create_am.sql
index 825aed325e..472289994e 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;
@@ -317,9 +314,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;
@@ -355,3 +356,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 2fe7b6dcc4..1409622374 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1229,6 +1229,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/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 5fabb127d7..a69ec1e1a8 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2580,6 +2580,7 @@ SeqScanState
 SeqTable
 SeqTableData
 SeqType
+SequenceAmRoutine
 SequenceItem
 SerCommitSeqNo
 SerialControl
@@ -3603,6 +3604,7 @@ lineno_t
 list_sort_comparator
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 local_ts_iter
 local_ts_radix_tree
@@ -3885,7 +3887,6 @@ scram_state
 scram_state_enum
 security_class_t
 sem_t
-sepgsql_context_info_t
 sequence_magic
 set_join_pathlist_hook_type
 set_rel_pathlist_hook_type
@@ -4109,6 +4110,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
@@ -4120,7 +4122,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.45.2

v9-0005-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From 60c72e4df87cc622ac7e5c9711b184ec20fcdbc2 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Aug 2024 13:25:19 +0900
Subject: [PATCH v9 5/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     | 49 +++++++++++++++++++++
 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, 207 insertions(+), 11 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 68ae2970ad..fb944c40b7 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -96,6 +96,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
@@ -186,6 +187,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 8c20c263c4..0eb5f9de3e 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -174,6 +174,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;
@@ -1224,6 +1225,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;
@@ -2379,6 +2381,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 */
 
@@ -2608,6 +2611,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);
@@ -2712,6 +2716,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);
 
@@ -3367,6 +3374,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;
 
@@ -3529,6 +3539,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.
  */
@@ -3738,6 +3799,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
 	_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);
 
@@ -4262,6 +4324,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);
@@ -5001,6 +5065,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -5060,6 +5125,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 ce5ed1dd39..cdfebcd340 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)
 
@@ -322,6 +323,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 */
 
@@ -353,6 +355,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;
@@ -394,6 +397,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 130b80775d..c52f84d4f8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -134,6 +134,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
@@ -477,6 +478,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},
@@ -1098,6 +1100,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;
@@ -1214,6 +1217,7 @@ help(const char *progname)
 	printf(_("  --no-publications            do not dump publications\n"));
 	printf(_("  --no-security-labels         do not dump security label assignments\n"));
 	printf(_("  --no-subscriptions           do not dump subscriptions\n"));
+	printf(_("  --no-sequence-access-method  do not 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"));
@@ -13112,6 +13116,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;
@@ -17393,26 +17400,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->schemaOnly && !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->schemaOnly && !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);
@@ -17432,6 +17453,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);
@@ -17503,6 +17528,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);
 	}
@@ -17625,6 +17651,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 e3ad8fb295..865f11702f 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -97,6 +97,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;
@@ -161,6 +162,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},
@@ -435,6 +437,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)
@@ -668,6 +672,7 @@ help(void)
 	printf(_("  --no-security-labels         do not dump security label assignments\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 df119591cc..081954c5ce 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -68,6 +68,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;
@@ -115,6 +116,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},
@@ -363,6 +365,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;
@@ -491,6 +494,7 @@ usage(const char *progname)
 	printf(_("  --no-publications            do not restore publications\n"));
 	printf(_("  --no-security-labels         do not restore security labels\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(_("  --section=SECTION            restore named section (pre-data, data, or post-data)\n"));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index ab6c830491..c4f3554829 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -537,6 +537,13 @@ 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', 'postgres',
+		],
+	},
 	no_table_access_method => {
 		dump_cmd => [
 			'pg_dump', '--no-sync',
@@ -711,6 +718,7 @@ my %full_runs = (
 	no_large_objects => 1,
 	no_owner => 1,
 	no_privs => 1,
+	no_sequence_access_method => 1,
 	no_table_access_method => 1,
 	pg_dumpall_dbprivs => 1,
 	pg_dumpall_exclude => 1,
@@ -4524,6 +4532,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
@@ -4552,6 +4572,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 ffc29b04fb..0c392a8080 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1121,6 +1121,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 4d7c046468..34643175fb 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -479,6 +479,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 b8b27e1719..922f69eb4a 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -733,6 +733,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.45.2

v9-0006-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From 4ffc8a56593071162fdf76e0462dd907347cbdb7 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v9 6/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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 08173ecb5c..f713ceb378 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8993,6 +8993,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 a7ff5f8264..07aa3f18c7 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -95,6 +95,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 7be25c5850..cc390e613a 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -257,6 +257,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 dae43dbaed..3067dc4d4d 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>SEQUENCE</literal> and
+      <literal>INDEX</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 1e283f13d1..52c6096e4b 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 0000000000..a96170bfac
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.45.2

v9-0007-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From 3eb134871b381465a5fcc98e84979e182259568b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 26 Apr 2024 15:09:00 +0900
Subject: [PATCH v9 7/7] snowflake: Add sequence AM based on it

This includes documentation and a basic implementation, though this is
in a very early stage.
---
 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            | 570 +++++++++++++++++++++++
 contrib/snowflake/snowflake.control      |   5 +
 contrib/snowflake/sql/snowflake.sql      |  29 ++
 13 files changed, 858 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 44639a8dca..e7b8871e8a 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -166,6 +166,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 07aa3f18c7..496ac19f25 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -156,6 +156,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 0000000000..060699e7ec
--- /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 abd780f277..f5d1b568b2 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -42,6 +42,7 @@ SUBDIRS = \
 		pg_walinspect	\
 		postgres_fdw	\
 		seg		\
+		snowflake	\
 		spi		\
 		tablefunc	\
 		tcn		\
diff --git a/contrib/meson.build b/contrib/meson.build
index 14a8906865..4f2603e7e9 100644
--- a/contrib/meson.build
+++ b/contrib/meson.build
@@ -57,6 +57,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 0000000000..44d119cfcc
--- /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 0000000000..fa5b48d565
--- /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 0000000000..b7e469bf73
--- /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 0000000000..567669eea7
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2024, 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 += bloom
+
+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 0000000000..bcb9d754f1
--- /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 0000000000..be4c4039ec
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,570 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2024, 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/sequenceam.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 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(snowflake_magic));
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+	sm->magic = SNOWFLAKE_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 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, (Item) 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)
+{
+	Page		page;
+	ItemId		lp;
+	snowflake_magic *sm;
+	Form_snowflake_data	seq;
+
+	*buf = ReadBuffer(rel, 0);
+	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(*buf);
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+
+	if (sm->magic != SNOWFLAKE_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_snowflake_data) GETSTRUCT(seqdatatuple);
+
+	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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_snowflake_data seq;
+
+	seq = read_snowflake(rel, &buf, &seqdatatuple);
+	*last_value = seq->count;
+	*is_called = seq->is_called;
+	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 0000000000..7b8c6089c2
--- /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 0000000000..395d166ba4
--- /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.45.2

#27Kirill Reshke
reshkekirill@gmail.com
In reply to: Michael Paquier (#26)
Re: Sequence Access Methods, round two

On Wed, 2 Oct 2024 at 10:29, Michael Paquier <michael@paquier.xyz> wrote:

Rebased as v29 due to an OID conflict.

Hi!
Looks like this needs another rebase

--
Best regards,
Kirill Reshke

#28Michael Paquier
michael@paquier.xyz
In reply to: Kirill Reshke (#27)
7 attachment(s)
Re: Sequence Access Methods, round two

On Tue, Dec 03, 2024 at 02:24:41PM +0500, Kirill Reshke wrote:

Looks like this needs another rebase

Indeed. There were a couple of blips in the backend and the dump
parts of the patch set. Rebased as v10 attached.
--
Michael

Attachments:

v10-0001-Remove-FormData_pg_sequence_data-from-init_param.patchtext/x-diff; charset=us-asciiDownload
From 712810957959e4a6a4211deaa0619cfe84ddf9f8 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:00:45 +0900
Subject: [PATCH v10 1/7] Remove FormData_pg_sequence_data from
 init_params()/sequence.c

init_params() sets up "last_value" and "is_called" for a sequence, based
on the sequence properties in pg_sequences.  This simplifies the logic
around log_cnt, which is reset to 0 when the metadata of a sequence is
expected to start from afresh when its properties are updated.
---
 src/backend/commands/sequence.c | 81 ++++++++++++++++++++-------------
 1 file changed, 49 insertions(+), 32 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0188e8bbd5..ed2cd2b1fc 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -106,7 +106,9 @@ static Form_pg_sequence_data read_seq_tuple(Relation rel,
 static void init_params(ParseState *pstate, List *options, bool for_identity,
 						bool isInit,
 						Form_pg_sequence seqform,
-						Form_pg_sequence_data seqdataform,
+						int64 *last_value,
+						bool *reset_state,
+						bool *is_called,
 						bool *need_seq_rewrite,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
@@ -121,7 +123,9 @@ ObjectAddress
 DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 {
 	FormData_pg_sequence seqform;
-	FormData_pg_sequence_data seqdataform;
+	int64		last_value;
+	bool		reset_state;
+	bool		is_called;
 	bool		need_seq_rewrite;
 	List	   *owned_by;
 	CreateStmt *stmt = makeNode(CreateStmt);
@@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* Check and set all option values */
 	init_params(pstate, seq->options, seq->for_identity, true,
-				&seqform, &seqdataform,
+				&seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/*
@@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 		{
 			case SEQ_COL_LASTVAL:
 				coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
-				value[i - 1] = Int64GetDatumFast(seqdataform.last_value);
+				value[i - 1] = Int64GetDatumFast(last_value);
 				break;
 			case SEQ_COL_LOG:
 				coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
@@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	ObjectAddress address;
 	Relation	rel;
 	HeapTuple	seqtuple;
+	bool		reset_state = false;
+	bool		is_called;
+	int64		last_value;
 	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
@@ -481,12 +488,14 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	/* 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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
-				seqform, newdataform,
+				seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/* If needed, rewrite the sequence relation itself */
@@ -513,6 +522,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * 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);
 	}
 
@@ -1236,17 +1249,19 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 /*
  * init_params: process the options list of CREATE or ALTER SEQUENCE, and
  * store the values into appropriate fields of seqform, for changes that go
- * into the pg_sequence catalog, and fields of seqdataform for changes to the
- * sequence relation itself.  Set *need_seq_rewrite to true if we changed any
- * parameters that require rewriting the sequence's relation (interesting for
- * ALTER SEQUENCE).  Also set *owned_by to any OWNED BY option, or to NIL if
- * there is none.
+ * into the pg_sequence catalog, and fields for changes to the sequence
+ * relation itself (is_called, last_value or any state it may hold).  Set
+ * *need_seq_rewrite to true if we changed any parameters that require
+ * rewriting the sequence's relation (interesting for ALTER SEQUENCE).  Also
+ * set *owned_by to any OWNED BY option, or to NIL if there is none.  Set
+ * *reset_state if the internal state of the sequence needs to change on a
+ * follow-up nextval().
  *
  * If isInit is true, fill any unspecified options with default values;
  * otherwise, do not change existing options that aren't explicitly overridden.
  *
  * Note: we force a sequence rewrite whenever we change parameters that affect
- * generation of future sequence values, even if the seqdataform per se is not
+ * generation of future sequence values, even if the metadata per se is not
  * changed.  This allows ALTER SEQUENCE to behave transactionally.  Currently,
  * the only option that doesn't cause that is OWNED BY.  It's *necessary* for
  * ALTER SEQUENCE OWNED BY to not rewrite the sequence, because that would
@@ -1257,7 +1272,9 @@ static void
 init_params(ParseState *pstate, List *options, bool for_identity,
 			bool isInit,
 			Form_pg_sequence seqform,
-			Form_pg_sequence_data seqdataform,
+			int64 *last_value,
+			bool *reset_state,
+			bool *is_called,
 			bool *need_seq_rewrite,
 			List **owned_by)
 {
@@ -1363,11 +1380,11 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	}
 
 	/*
-	 * We must reset log_cnt when isInit or when changing any parameters that
-	 * would affect future nextval allocations.
+	 * We must reset the state when isInit or when changing any parameters
+	 * that would affect future nextval allocations.
 	 */
 	if (isInit)
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 
 	/* AS type */
 	if (as_type != NULL)
@@ -1416,7 +1433,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("INCREMENT must not be zero")));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1428,7 +1445,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	{
 		seqform->seqcycle = boolVal(is_cycled->arg);
 		Assert(BoolIsValid(seqform->seqcycle));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1439,7 +1456,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (max_value != NULL && max_value->arg)
 	{
 		seqform->seqmax = defGetInt64(max_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || max_value != NULL || reset_max_value)
 	{
@@ -1455,7 +1472,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmax = -1;	/* descending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate maximum value.  No need to check INT8 as seqmax is an int64 */
@@ -1471,7 +1488,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (min_value != NULL && min_value->arg)
 	{
 		seqform->seqmin = defGetInt64(min_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || min_value != NULL || reset_min_value)
 	{
@@ -1487,7 +1504,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmin = 1;	/* ascending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate minimum value.  No need to check INT8 as seqmin is an int64 */
@@ -1538,30 +1555,30 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (restart_value != NULL)
 	{
 		if (restart_value->arg != NULL)
-			seqdataform->last_value = defGetInt64(restart_value);
+			*last_value = defGetInt64(restart_value);
 		else
-			seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
-		seqdataform->log_cnt = 0;
+			*last_value = seqform->seqstart;
+		*is_called = false;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-		seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
+		*last_value = seqform->seqstart;
+		*is_called = false;
 	}
 
 	/* crosscheck RESTART (or current value, if changing MIN/MAX) */
-	if (seqdataform->last_value < seqform->seqmin)
+	if (*last_value < seqform->seqmin)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be less than MINVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmin)));
-	if (seqdataform->last_value > seqform->seqmax)
+	if (*last_value > seqform->seqmax)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be greater than MAXVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmax)));
 
 	/* CACHE */
@@ -1573,7 +1590,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("CACHE (%lld) must be greater than zero",
 							(long long) seqform->seqcache)));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-- 
2.45.2

v10-0002-Integrate-addition-of-attributes-for-sequences-w.patchtext/x-diff; charset=us-asciiDownload
From c171568fa26c03fe40541c5c572811bb33c432f7 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v10 2/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               | 29 +++++++++++++++++--
 src/backend/commands/tablecmds.c              | 10 +++++++
 src/backend/tcop/utility.c                    |  4 +++
 .../test_ddl_deparse/expected/alter_table.out | 10 +++++--
 .../expected/create_sequence_1.out            |  5 +++-
 .../expected/create_table.out                 | 15 ++++++++--
 .../test_ddl_deparse/test_ddl_deparse.c       |  3 ++
 8 files changed, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 0f9462493e..26f0b45c7e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2358,6 +2358,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 ed2cd2b1fc..158ea79c66 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -136,6 +136,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;
@@ -174,7 +177,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;
@@ -198,7 +200,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;
@@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 6ccae4cb4a..390b029394 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4569,6 +4569,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 */
@@ -4864,6 +4865,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,
@@ -5294,6 +5302,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,
@@ -6446,6 +6455,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 f28bf37105..ac6536e59e 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 50d0354a34..ed31059ef5 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
@@ -70,7 +73,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_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
index 5837ea484e..310ce5a6ba 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.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 14915f661a..527c67995a 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 98d237ac68..8cc073bdc1 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.45.2

v10-0003-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From eff690308995c3d128daa70bde340e8bd0c11a2a Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 25 Apr 2024 16:49:47 +0900
Subject: [PATCH v10 3/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.
- seqlocalxlog.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/seqlocalam.h               |  58 ++
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 .../rmgrdesc/{seqdesc.c => seqlocaldesc.c}    |  20 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/meson.build       |   2 +
 src/backend/access/sequence/seqlocalam.c      | 634 ++++++++++++++++++
 src/backend/access/sequence/seqlocalxlog.c    |  82 +++
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 634 +-----------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 14 files changed, 822 insertions(+), 622 deletions(-)
 create mode 100644 src/include/access/seqlocalam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%)
 create mode 100644 src/backend/access/sequence/seqlocalam.c
 create mode 100644 src/backend/access/sequence/seqlocalxlog.c

diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 78e6b908c6..fa94beb6ed 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/seqlocalam.h b/src/include/access/seqlocalam.h
new file mode 100644
index 0000000000..225fb9a2cb
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2024, 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 "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_SEQ_LOCAL_LOG			0x00
+
+typedef struct xl_seq_local_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_seq_local_rec;
+
+/*
+ * 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;
+
+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);
+
+/* 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);
+extern void seq_local_change_persistence(Relation rel,
+										 char newrelpersistence);
+
+#endif							/* SEQLOCALAM_H */
diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile
index cd95eec37f..e5900ed77a 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 e8b7a65fc7..e390d385bf 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 cf0e02ded5..409c67335e 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.c
  *
  * Portions Copyright (c) 1996-2024, 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.h"
+#include "access/seqlocalam.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 9f9d31f542..a15ceec1c0 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 1843aed38d..4d24f900b6 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2024, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'seqlocalam.c',
+  'seqlocalxlog.c',
   'sequence.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
new file mode 100644
index 0000000000..b0a0d72966
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -0,0 +1,634 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.c
+ *	  Local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, 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/multixact.h"
+#include "access/seqlocalam.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"
+
+
+/*
+ * 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
+
+/* 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, (Item) 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((char *) &xlrec, sizeof(xl_seq_local_rec));
+		XLogRegisterData((char *) 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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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((char *) &xlrec, sizeof(xl_seq_local_rec));
+		XLogRegisterData((char *) 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 "heap";
+}
+
+/*
+ * 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((char *) &xlrec, sizeof(xl_seq_local_rec));
+		XLogRegisterData((char *) 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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_seq_local_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	*last_value = seq->last_value;
+	*is_called = seq->is_called;
+
+	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/sequence/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c
new file mode 100644
index 0000000000..db0ad969db
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalxlog.c
@@ -0,0 +1,82 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalxlog.c
+ *	  WAL replay logic for local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/access/sequence/seqlocalxlog.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/seqlocalam.h"
+#include "access/xlogutils.h"
+#include "storage/block.h"
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+seq_local_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+seq_local_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record);
+	seq_local_magic *sm;
+
+	if (info != XLOG_SEQ_LOCAL_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = (Page) BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	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_local_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec);
+
+	if (PageAddItem(localpage, (Item) item, itemsz,
+					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "seq_local_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 1b7499726e..cc92268937 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 158ea79c66..1cad06da66 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -50,23 +51,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -96,13 +80,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,
@@ -134,14 +114,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
@@ -174,35 +148,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;
@@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	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)
@@ -292,10 +222,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;
@@ -306,7 +232,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))
@@ -315,40 +240,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 */
@@ -357,106 +250,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, (Item) 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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) tuple->t_data, tuple->t_len);
-
-		recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	UnlockReleaseBuffer(buf);
-}
-
 /*
  * AlterSequence
  *
@@ -468,10 +261,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;
@@ -480,7 +270,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	bool		reset_state = false;
 	bool		is_called;
 	int64		last_value;
-	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
 	relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -507,16 +296,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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -526,32 +307,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 */
@@ -580,8 +339,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
@@ -596,10 +353,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);
 }
@@ -662,24 +416,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);
@@ -724,105 +469,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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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;
@@ -832,69 +481,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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) 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;
@@ -984,9 +570,6 @@ do_setval(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,
@@ -1020,9 +603,6 @@ do_setval(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),
@@ -1044,37 +624,8 @@ do_setval(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((char *) &xlrec, sizeof(xl_seq_rec));
-		XLogRegisterData((char *) 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);
 }
@@ -1215,62 +766,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
@@ -1855,16 +1350,13 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		Buffer		buf;
-		HeapTupleData seqtuple;
-		Form_pg_sequence_data seq;
+		bool		is_called;
+		int64		last_value;
 
-		seq = read_seq_tuple(seqrel, &buf, &seqtuple);
+		seq_local_get_state(seqrel, &last_value, &is_called);
 
-		values[0] = Int64GetDatum(seq->last_value);
-		values[1] = BoolGetDatum(seq->is_called);
-
-		UnlockReleaseBuffer(buf);
+		values[0] = Int64GetDatum(last_value);
+		values[1] = BoolGetDatum(is_called);
 	}
 	else
 		memset(isnull, true, sizeof(isnull));
@@ -1908,17 +1400,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);
 	}
+
 	sequence_close(seqrel, NoLock);
 
 	if (is_called)
@@ -1927,57 +1411,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
-
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = (Page) BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, (Item) item, itemsz,
-					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1992,14 +1425,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c76..8d1195de26 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/seqlocaldesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index 6b8c17bb4c..2fccc7a4e2 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index 578e473139..0b911048e1 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.45.2

v10-0004-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From a63c7d6672159d5e4c5fb7b0b7e6f456f67e48ed Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 4 Dec 2024 08:09:46 +0900
Subject: [PATCH v10 4/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               |  15 --
 src/include/access/sequenceam.h               | 181 ++++++++++++++++++
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/seqlocalam.c      |  41 +++-
 src/backend/access/sequence/sequence.c        |   3 +-
 src/backend/access/sequence/sequenceamapi.c   | 145 ++++++++++++++
 src/backend/catalog/heap.c                    |   6 +-
 src/backend/commands/amcmds.c                 |  16 ++
 src/backend/commands/sequence.c               |  23 +--
 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_tables.c           |  12 ++
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/bin/psql/describe.c                       |   2 +
 src/bin/psql/tab-complete.in.c                |   4 +-
 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 +-
 40 files changed, 695 insertions(+), 182 deletions(-)
 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
index 225fb9a2cb..21936511ac 100644
--- a/src/include/access/seqlocalam.h
+++ b/src/include/access/seqlocalam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_SEQ_LOCAL_LOG			0x00
@@ -41,18 +40,4 @@ 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);
 
-/* 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);
-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 0000000000..ac48c8b468
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,181 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2024, 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.
+	 */
+	void		(*get_state) (Relation rel, int64 *last_value, bool *is_called);
+
+	/*
+	 * 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)
+{
+	rel->rd_sequenceam->get_state(rel, last_value, is_called);
+}
+
+/*
+ * 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 db87490282..ba9766fae4 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 475593fad4..3a94e8c636 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 0fc2c093b0..21623f34fa 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -228,6 +228,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)
+
 extern int	errdetail_relkind_not_supported(char relkind);
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9575524007..2d205649f6 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',
@@ -7765,6 +7771,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 ceff66ccde..0eba013687 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -627,6 +627,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 5fd095ea17..a43a6cbf85 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -145,6 +145,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 e88cbee3b5..9b22b2f248 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index b665e55b65..774ae2652c 100644
--- a/src/include/nodes/meson.build
+++ b/src/include/nodes/meson.build
@@ -9,6 +9,7 @@ node_support_input_i = [
   '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/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 26f0b45c7e..9aff80ea34 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3148,6 +3148,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 5813dba0a2..f4a49969b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -53,6 +53,8 @@ extern bool check_debug_io_direct(char **newval, void **extra, GucSource source)
 extern void assign_debug_io_direct(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 8700204953..2a4b7bed2b 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 a15ceec1c0..62006165a1 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = seqlocalam.o seqlocalxlog.o sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 4d24f900b6..ea91990552 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -4,4 +4,5 @@ backend_sources += files(
   'seqlocalam.c',
   'seqlocalxlog.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
index b0a0d72966..2e524c9e99 100644
--- a/src/backend/access/sequence/seqlocalam.c
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -17,6 +17,7 @@
 
 #include "access/multixact.h"
 #include "access/seqlocalam.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -24,6 +25,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -230,10 +232,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;
@@ -417,7 +419,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 "heap";
@@ -432,7 +434,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];
@@ -499,7 +501,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;
@@ -547,7 +549,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;
@@ -600,7 +602,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)
 {
 	Buffer		buf;
@@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called)
  *
  * Persistence change for the local sequence Relation.
  */
-void
+static void
 seq_local_change_persistence(Relation rel, char newrelpersistence)
 {
 	Buffer		buf;
@@ -632,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 d2cf95aadc..8669b20c26 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 0000000000..dd1a60d827
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2024, 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/xact.h"
+#include "access/sequenceam.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 d7b88b61dc..cd0cb4784b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1473,9 +1473,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 aaa0f9a1dc..1a27f191a3 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 1cad06da66..a65e93bbee 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -152,6 +152,7 @@ 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;
@@ -173,7 +174,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)
@@ -241,7 +242,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 */
@@ -297,7 +298,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
 
 	/* Read sequence data */
-	seq_local_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -310,7 +311,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 */
@@ -353,7 +354,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);
 }
@@ -470,8 +471,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;
@@ -625,7 +626,7 @@ do_setval(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);
 }
@@ -1353,7 +1354,7 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		bool		is_called;
 		int64		last_value;
 
-		seq_local_get_state(seqrel, &last_value, &is_called);
+		sequence_get_state(seqrel, &last_value, &is_called);
 
 		values[0] = Int64GetDatum(last_value);
 		values[1] = BoolGetDatum(is_called);
@@ -1400,7 +1401,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		seq_local_get_state(seqrel, &result, &is_called);
+		sequence_get_state(seqrel, &result, &is_called);
 	}
 
 	sequence_close(seqrel, NoLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 390b029394..858c4aded9 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"
@@ -983,14 +984,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)
 	{
@@ -1003,6 +1008,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 constraints are passed in
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 66bbad8e6e..86b1773c0b 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -47,6 +47,7 @@ node_headers = \
 	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/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 81df3bdf95..d2450e559d 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -59,6 +59,7 @@ my @all_input_files = qw(
   nodes/execnodes.h
   access/amapi.h
   access/sdir.h
+  access/sequenceam.h
   access/tableam.h
   access/tsmapi.h
   commands/event_trigger.h
@@ -83,6 +84,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 67eb96396a..b7a8948ab3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -377,6 +377,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
@@ -4884,23 +4885,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;
@@ -4937,6 +4941,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); }
 		;
@@ -5940,6 +5949,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 0f324ee4e3..3b0379d628 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"
@@ -518,6 +519,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 e189e9b79d..e31dbb0ebb 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 d0892cee24..0008744458 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);
@@ -1205,8 +1208,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)
 	{
@@ -1215,6 +1217,8 @@ retry:
 		 * inherit.
 		 */
 	}
+	else if (RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind))
+		RelationInitSequenceAccessMethod(relation);
 	else
 		Assert(relation->rd_rel->relam == InvalidOid);
 
@@ -1811,17 +1815,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.
@@ -1852,6 +1848,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
  *
@@ -3667,14 +3706,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
@@ -4298,13 +4340,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);
@@ -6320,8 +6370,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);
@@ -6333,6 +6385,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_tables.c b/src/backend/utils/misc/guc_tables.c
index 8cf1afbad2..c28f85e7bf 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -30,6 +30,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"
@@ -4204,6 +4205,17 @@ struct config_string ConfigureNamesString[] =
 		check_default_table_access_method, NULL, NULL
 	},
 
+	{
+		{"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets the default sequence access method for new sequences."),
+			NULL,
+			GUC_IS_NAME
+		},
+		&default_sequence_access_method,
+		DEFAULT_SEQUENCE_ACCESS_METHOD,
+		check_default_sequence_access_method, NULL, NULL
+	},
+
 	{
 		{"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default tablespace to create tables and indexes in."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index a2ac7575ca..a049a7d424 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -706,6 +706,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 2657abdc72..0428d44793 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -167,10 +167,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 bbd08770c3..45dbae55a2 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2532,7 +2532,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");
@@ -3557,7 +3557,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 c1a9515725..784870e603 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 34a32bd11d..8bc2238e60 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1935,6 +1935,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 36dc31c16c..ac4848833e 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5064,31 +5064,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
@@ -5113,32 +5115,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 88d8f6c32d..de2796ae05 100644
--- a/src/test/regress/expected/type_sanity.out
+++ b/src/test/regress/expected/type_sanity.out
@@ -507,21 +507,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 754fe0c694..76a91cf8dd 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 2fe7b6dcc4..1409622374 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1229,6 +1229,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 e88d6cbe49..1ff8580a79 100644
--- a/src/test/regress/sql/type_sanity.sql
+++ b/src/test/regress/sql/type_sanity.sql
@@ -364,18 +364,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 2d4c870423..9d08f9cd38 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2585,6 +2585,7 @@ SeqScanState
 SeqTable
 SeqTableData
 SeqType
+SequenceAmRoutine
 SequenceItem
 SerCommitSeqNo
 SerialControl
@@ -3612,6 +3613,7 @@ list_sort_comparator
 loc_chunk
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 local_ts_iter
 local_ts_radix_tree
@@ -3896,7 +3898,6 @@ scram_state_enum
 script_error_callback_arg
 security_class_t
 sem_t
-sepgsql_context_info_t
 sequence_magic
 set_join_pathlist_hook_type
 set_rel_pathlist_hook_type
@@ -4120,6 +4121,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
@@ -4131,7 +4133,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.45.2

v10-0005-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From 696f6a4471aabd54cbbec8c85e179a335df8f32b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Aug 2024 13:25:19 +0900
Subject: [PATCH v10 5/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     | 49 +++++++++++++++++++++
 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, 207 insertions(+), 11 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index f0f19bb0b2..2b8298f194 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -96,6 +96,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
@@ -186,6 +187,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 59e7941a0c..f3b5cf6e57 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -176,6 +176,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;
@@ -1228,6 +1229,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;
@@ -2383,6 +2385,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 */
 
@@ -2612,6 +2615,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);
@@ -2716,6 +2720,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);
 
@@ -3371,6 +3378,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;
 
@@ -3533,6 +3543,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.
  */
@@ -3742,6 +3803,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
 	_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);
 
@@ -4266,6 +4328,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);
@@ -5005,6 +5069,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -5064,6 +5129,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 ce5ed1dd39..cdfebcd340 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)
 
@@ -322,6 +323,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 */
 
@@ -353,6 +355,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;
@@ -394,6 +397,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 ec0cdf4ed7..4a970b2e80 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -132,6 +132,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
@@ -481,6 +482,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},
@@ -1106,6 +1108,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;
@@ -1222,6 +1225,7 @@ help(const char *progname)
 	printf(_("  --no-publications            do not dump publications\n"));
 	printf(_("  --no-security-labels         do not dump security label assignments\n"));
 	printf(_("  --no-subscriptions           do not dump subscriptions\n"));
+	printf(_("  --no-sequence-access-method  do not 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"));
@@ -13284,6 +13288,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;
@@ -17661,26 +17668,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);
@@ -17700,6 +17721,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);
@@ -17771,6 +17796,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);
 	}
@@ -17893,6 +17919,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 9a04e51c81..036f756240 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -97,6 +97,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;
@@ -161,6 +162,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},
@@ -435,6 +437,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)
@@ -668,6 +672,7 @@ help(void)
 	printf(_("  --no-security-labels         do not dump security label assignments\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 88ae39d938..08531eac0a 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -67,6 +67,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;
@@ -116,6 +117,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},
@@ -368,6 +370,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;
@@ -496,6 +499,7 @@ usage(const char *progname)
 	printf(_("  --no-publications            do not restore publications\n"));
 	printf(_("  --no-security-labels         do not restore security labels\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(_("  --section=SECTION            restore named section (pre-data, data, or post-data)\n"));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index aa1564cd45..80324986b4 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -537,6 +537,13 @@ 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', 'postgres',
+		],
+	},
 	no_table_access_method => {
 		dump_cmd => [
 			'pg_dump', '--no-sync',
@@ -711,6 +718,7 @@ my %full_runs = (
 	no_large_objects => 1,
 	no_owner => 1,
 	no_privs => 1,
+	no_sequence_access_method => 1,
 	no_table_access_method => 1,
 	pg_dumpall_dbprivs => 1,
 	pg_dumpall_exclude => 1,
@@ -4534,6 +4542,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
@@ -4562,6 +4582,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 d66e901f51..4e0cdc841c 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1121,6 +1121,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 014f279258..339d7c7985 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -479,6 +479,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 b8b27e1719..922f69eb4a 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -733,6 +733,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.45.2

v10-0006-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From ede1d0343cfb75366c3f3c50c268cb5072344702 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v10 6/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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 e0c8325a39..2b49b2fa33 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9065,6 +9065,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 66e6dccd4c..4c367bd5fe 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -95,6 +95,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 7be25c5850..cc390e613a 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -257,6 +257,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 dae43dbaed..3067dc4d4d 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>SEQUENCE</literal> and
+      <literal>INDEX</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 1e283f13d1..52c6096e4b 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 0000000000..a96170bfac
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.45.2

v10-0007-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From 87b21f2b24e5f123c299a200ad5d2be100c8469a Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 26 Apr 2024 15:09:00 +0900
Subject: [PATCH v10 7/7] snowflake: Add sequence AM based on it

This includes documentation and a basic implementation, though this is
in a very early stage.
---
 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            | 570 +++++++++++++++++++++++
 contrib/snowflake/snowflake.control      |   5 +
 contrib/snowflake/sql/snowflake.sql      |  29 ++
 13 files changed, 858 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 7c381949a5..7f55133bd2 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -167,6 +167,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 4c367bd5fe..5247bee322 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -157,6 +157,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 0000000000..060699e7ec
--- /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 952855d9b6..26ec317349 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -43,6 +43,7 @@ SUBDIRS = \
 		pg_walinspect	\
 		postgres_fdw	\
 		seg		\
+		snowflake	\
 		spi		\
 		tablefunc	\
 		tcn		\
diff --git a/contrib/meson.build b/contrib/meson.build
index 159ff41555..3652c85ac8 100644
--- a/contrib/meson.build
+++ b/contrib/meson.build
@@ -58,6 +58,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 0000000000..44d119cfcc
--- /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 0000000000..fa5b48d565
--- /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 0000000000..b7e469bf73
--- /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 0000000000..567669eea7
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2024, 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 += bloom
+
+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 0000000000..bcb9d754f1
--- /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 0000000000..be4c4039ec
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,570 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2024, 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/sequenceam.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 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(snowflake_magic));
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+	sm->magic = SNOWFLAKE_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 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, (Item) 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)
+{
+	Page		page;
+	ItemId		lp;
+	snowflake_magic *sm;
+	Form_snowflake_data	seq;
+
+	*buf = ReadBuffer(rel, 0);
+	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(*buf);
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+
+	if (sm->magic != SNOWFLAKE_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_snowflake_data) GETSTRUCT(seqdatatuple);
+
+	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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_snowflake_data seq;
+
+	seq = read_snowflake(rel, &buf, &seqdatatuple);
+	*last_value = seq->count;
+	*is_called = seq->is_called;
+	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 0000000000..7b8c6089c2
--- /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 0000000000..395d166ba4
--- /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.45.2

#29Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#28)
7 attachment(s)
Re: Sequence Access Methods, round two

On Wed, Dec 04, 2024 at 08:36:56AM +0900, Michael Paquier wrote:

Indeed. There were a couple of blips in the backend and the dump
parts of the patch set. Rebased as v10 attached.

Conflicts caused by ed5e5f071033 in the third patch in sequence.c with
adjustments in the new files seqlocalam.c were required, hence here is
a rebased version labelled v11.
--
Michael

Attachments:

v11-0002-Integrate-addition-of-attributes-for-sequences-w.patchtext/x-diff; charset=us-asciiDownload
From c130d7d724f597b31ba6b7e04cb089fa5cb0bcc8 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v11 2/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               | 29 +++++++++++++++++--
 src/backend/commands/tablecmds.c              | 10 +++++++
 src/backend/tcop/utility.c                    |  4 +++
 .../test_ddl_deparse/expected/alter_table.out | 10 +++++--
 .../expected/create_sequence_1.out            |  5 +++-
 .../expected/create_table.out                 | 15 ++++++++--
 .../test_ddl_deparse/test_ddl_deparse.c       |  3 ++
 8 files changed, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8dd421fa0ef3..30cd7188c5c1 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2402,6 +2402,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 576c2ec502db..a902c6ce6f09 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -136,6 +136,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;
@@ -174,7 +177,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;
@@ -198,7 +200,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;
@@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 72a1b64c2a22..e466508c1d3b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4619,6 +4619,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 */
@@ -4914,6 +4915,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,
@@ -5344,6 +5352,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,
@@ -6501,6 +6510,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 25fe3d580166..0629dc5387c0 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 50d0354a3417..ed31059ef584 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
@@ -70,7 +73,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_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
index 5837ea484e40..310ce5a6baf5 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.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 193669f2bc1e..254fdf90c79a 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.47.2

v11-0003-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From a76e0133e5d177d9ebe9c08b34aaf6f18191d82c Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 25 Apr 2024 16:49:47 +0900
Subject: [PATCH v11 3/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.
- seqlocalxlog.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/seqlocalam.h               |  58 ++
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 .../rmgrdesc/{seqdesc.c => seqlocaldesc.c}    |  20 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/meson.build       |   2 +
 src/backend/access/sequence/seqlocalam.c      | 634 ++++++++++++++++++
 src/backend/access/sequence/seqlocalxlog.c    |  82 +++
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 634 +-----------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 14 files changed, 822 insertions(+), 622 deletions(-)
 create mode 100644 src/include/access/seqlocalam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%)
 create mode 100644 src/backend/access/sequence/seqlocalam.c
 create mode 100644 src/backend/access/sequence/seqlocalxlog.c

diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 8e7fc9db8778..b942b25350bc 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/seqlocalam.h b/src/include/access/seqlocalam.h
new file mode 100644
index 000000000000..225fb9a2cbeb
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2024, 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 "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_SEQ_LOCAL_LOG			0x00
+
+typedef struct xl_seq_local_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_seq_local_rec;
+
+/*
+ * 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;
+
+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);
+
+/* 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);
+extern void seq_local_change_persistence(Relation rel,
+										 char newrelpersistence);
+
+#endif							/* SEQLOCALAM_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 96c98e800c22..6b3b3e1e44c1 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 0d289d77fcf7..6ffbcb2c4735 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.c
  *
  * Portions Copyright (c) 1996-2025, 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.h"
+#include "access/seqlocalam.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..a15ceec1c0a0 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 ec9ab9b7e9db..8bc0e95e68c0 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2025, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'seqlocalam.c',
+  'seqlocalxlog.c',
   'sequence.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
new file mode 100644
index 000000000000..bc735ebb0838
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -0,0 +1,634 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.c
+ *	  Local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, 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/multixact.h"
+#include "access/seqlocalam.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"
+
+
+/*
+ * 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
+
+/* 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, (Item) 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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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 "heap";
+}
+
+/*
+ * 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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_seq_local_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	*last_value = seq->last_value;
+	*is_called = seq->is_called;
+
+	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/sequence/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c
new file mode 100644
index 000000000000..db0ad969dbf2
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalxlog.c
@@ -0,0 +1,82 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalxlog.c
+ *	  WAL replay logic for local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/access/sequence/seqlocalxlog.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/seqlocalam.h"
+#include "access/xlogutils.h"
+#include "storage/block.h"
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+seq_local_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+seq_local_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record);
+	seq_local_magic *sm;
+
+	if (info != XLOG_SEQ_LOCAL_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = (Page) BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	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_local_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec);
+
+	if (PageAddItem(localpage, (Item) item, itemsz,
+					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "seq_local_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 1b7499726eb0..cc92268937b1 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index a902c6ce6f09..b7ed47d92801 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -50,23 +51,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -96,13 +80,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,
@@ -134,14 +114,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
@@ -174,35 +148,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;
@@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	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)
@@ -292,10 +222,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;
@@ -306,7 +232,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))
@@ -315,40 +240,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 */
@@ -357,106 +250,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, (Item) 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
  *
@@ -468,10 +261,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;
@@ -480,7 +270,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	bool		reset_state = false;
 	bool		is_called;
 	int64		last_value;
-	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
 	relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -507,16 +296,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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -526,32 +307,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 */
@@ -580,8 +339,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
@@ -596,10 +353,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);
 }
@@ -662,24 +416,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);
@@ -724,105 +469,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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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;
@@ -832,69 +481,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;
@@ -984,9 +570,6 @@ do_setval(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,
@@ -1020,9 +603,6 @@ do_setval(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),
@@ -1044,37 +624,8 @@ do_setval(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);
 }
@@ -1215,62 +766,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
@@ -1855,16 +1350,13 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		Buffer		buf;
-		HeapTupleData seqtuple;
-		Form_pg_sequence_data seq;
+		bool		is_called;
+		int64		last_value;
 
-		seq = read_seq_tuple(seqrel, &buf, &seqtuple);
+		seq_local_get_state(seqrel, &last_value, &is_called);
 
-		values[0] = Int64GetDatum(seq->last_value);
-		values[1] = BoolGetDatum(seq->is_called);
-
-		UnlockReleaseBuffer(buf);
+		values[0] = Int64GetDatum(last_value);
+		values[1] = BoolGetDatum(is_called);
 	}
 	else
 		memset(isnull, true, sizeof(isnull));
@@ -1908,17 +1400,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);
 	}
+
 	sequence_close(seqrel, NoLock);
 
 	if (is_called)
@@ -1927,57 +1411,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
-
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = (Page) BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, (Item) item, itemsz,
-					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1992,14 +1425,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c767e..8d1195de2637 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/seqlocaldesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index 6b8c17bb4c4e..2fccc7a4e2bd 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index 5c8fea275bb5..56a6e93a9a77 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.47.2

v11-0004-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From f35a5db2325dcbc3b524c3af371e2eb49a60f469 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 4 Dec 2024 08:09:46 +0900
Subject: [PATCH v11 4/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               |  15 --
 src/include/access/sequenceam.h               | 181 ++++++++++++++++++
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/seqlocalam.c      |  41 +++-
 src/backend/access/sequence/sequence.c        |   3 +-
 src/backend/access/sequence/sequenceamapi.c   | 145 ++++++++++++++
 src/backend/catalog/heap.c                    |   6 +-
 src/backend/commands/amcmds.c                 |  16 ++
 src/backend/commands/sequence.c               |  23 +--
 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_tables.c           |  12 ++
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/bin/psql/describe.c                       |   2 +
 src/bin/psql/tab-complete.in.c                |   4 +-
 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 +-
 40 files changed, 695 insertions(+), 182 deletions(-)
 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
index 225fb9a2cbeb..21936511ac2b 100644
--- a/src/include/access/seqlocalam.h
+++ b/src/include/access/seqlocalam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_SEQ_LOCAL_LOG			0x00
@@ -41,18 +40,4 @@ 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);
 
-/* 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);
-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..ac48c8b468be
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,181 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2024, 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.
+	 */
+	void		(*get_state) (Relation rel, int64 *last_value, bool *is_called);
+
+	/*
+	 * 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)
+{
+	rel->rd_sequenceam->get_state(rel, last_value, is_called);
+}
+
+/*
+ * 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 26d15928a155..8f076fcec958 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 6e98a0930c27..080bea5031a9 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 f0d612ca4877..f217c8d8fb07 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -228,6 +228,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)
+
 extern int	errdetail_relkind_not_supported(char relkind);
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9e803d610d7b..71ba82be775a 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',
@@ -7803,6 +7809,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 6dca77e0a22f..c42531b25533 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -627,6 +627,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 6d9348bac804..eb2e344a184f 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -145,6 +145,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 9ac0b67683d3..7693e9941fc9 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index d1ca24dd32f0..b1c4155c9a91 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 30cd7188c5c1..95e67fd1235e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3196,6 +3196,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 87999218d687..7464471f15d1 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -53,6 +53,8 @@ extern bool check_debug_io_direct(char **newval, void **extra, GucSource source)
 extern void assign_debug_io_direct(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 db3e504c3d2d..f2155c1195d8 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 a15ceec1c0a0..62006165a15f 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = seqlocalam.o seqlocalxlog.o sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 8bc0e95e68c0..d82af34d538c 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -4,4 +4,5 @@ backend_sources += files(
   'seqlocalam.c',
   'seqlocalxlog.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
index bc735ebb0838..946c7f87a686 100644
--- a/src/backend/access/sequence/seqlocalam.c
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -17,6 +17,7 @@
 
 #include "access/multixact.h"
 #include "access/seqlocalam.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -24,6 +25,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -230,10 +232,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;
@@ -417,7 +419,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 "heap";
@@ -432,7 +434,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];
@@ -499,7 +501,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;
@@ -547,7 +549,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;
@@ -600,7 +602,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)
 {
 	Buffer		buf;
@@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called)
  *
  * Persistence change for the local sequence Relation.
  */
-void
+static void
 seq_local_change_persistence(Relation rel, char newrelpersistence)
 {
 	Buffer		buf;
@@ -632,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 8b5303553702..f30317d1feae 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..dd1a60d827a4
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2024, 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/xact.h"
+#include "access/sequenceam.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 956f196fc95c..4d06010b9a88 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1477,9 +1477,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 58ed9d216cc0..e44633d13b60 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 b7ed47d92801..e88ace9b4a4d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -152,6 +152,7 @@ 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;
@@ -173,7 +174,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)
@@ -241,7 +242,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 */
@@ -297,7 +298,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
 
 	/* Read sequence data */
-	seq_local_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -310,7 +311,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 */
@@ -353,7 +354,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);
 }
@@ -470,8 +471,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;
@@ -625,7 +626,7 @@ do_setval(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);
 }
@@ -1353,7 +1354,7 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		bool		is_called;
 		int64		last_value;
 
-		seq_local_get_state(seqrel, &last_value, &is_called);
+		sequence_get_state(seqrel, &last_value, &is_called);
 
 		values[0] = Int64GetDatum(last_value);
 		values[1] = BoolGetDatum(is_called);
@@ -1400,7 +1401,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		seq_local_get_state(seqrel, &result, &is_called);
+		sequence_get_state(seqrel, &result, &is_called);
 	}
 
 	sequence_close(seqrel, NoLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e466508c1d3b..6102966d8d6a 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"
@@ -998,14 +999,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)
 	{
@@ -1018,6 +1023,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 1a657f7e0aea..162ee16dbf6d 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 d3887628d469..b5e72aeec8fb 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -381,6 +381,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
@@ -4919,23 +4920,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;
@@ -4972,6 +4976,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); }
 		;
@@ -5975,6 +5984,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 eb7716cd84c9..8fcad04428e5 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"
@@ -518,6 +519,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 317a1f2b282f..68f160dda7d3 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 398114373e9f..c10e77372071 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);
@@ -1196,8 +1199,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)
 	{
@@ -1206,6 +1208,8 @@ retry:
 		 * inherit.
 		 */
 	}
+	else if (RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind))
+		RelationInitSequenceAccessMethod(relation);
 	else
 		Assert(relation->rd_rel->relam == InvalidOid);
 
@@ -1802,17 +1806,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.
@@ -1843,6 +1839,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
  *
@@ -3659,14 +3698,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
@@ -4290,13 +4332,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);
@@ -6348,8 +6398,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);
@@ -6361,6 +6413,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_tables.c b/src/backend/utils/misc/guc_tables.c
index cce733146090..6a9f880da169 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -30,6 +30,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"
@@ -4252,6 +4253,17 @@ struct config_string ConfigureNamesString[] =
 		check_default_table_access_method, NULL, NULL
 	},
 
+	{
+		{"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets the default sequence access method for new sequences."),
+			NULL,
+			GUC_IS_NAME
+		},
+		&default_sequence_access_method,
+		DEFAULT_SEQUENCE_ACCESS_METHOD,
+		check_default_sequence_access_method, NULL, NULL
+	},
+
 	{
 		{"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default tablespace to create tables and indexes in."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index d472987ed46a..a6426a7a2fb3 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -725,6 +725,7 @@ autovacuum_worker_slots = 16	# autovacuum worker slots to allocate
 					#   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 3b7ba66fad05..6cdb1b075625 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 eb8bc1287202..f4b61948f8e1 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2561,7 +2561,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");
@@ -3601,7 +3601,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 b673642ad1d7..3dedeff17201 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1940,6 +1940,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 f9db4032e1f1..2cb070b12366 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5120,31 +5120,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
@@ -5169,32 +5171,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 8eff3d10d279..622f360d5b3c 100644
--- a/src/test/regress/expected/type_sanity.out
+++ b/src/test/regress/expected/type_sanity.out
@@ -507,21 +507,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 2fb3a8528781..e60eeab33c02 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 303f90955d15..aade05c0e172 100644
--- a/src/test/regress/sql/type_sanity.sql
+++ b/src/test/regress/sql/type_sanity.sql
@@ -364,18 +364,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 bce4214503d6..ed72179081e9 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2601,6 +2601,7 @@ SeqScanState
 SeqTable
 SeqTableData
 SeqType
+SequenceAmRoutine
 SequenceItem
 SerCommitSeqNo
 SerialControl
@@ -3638,6 +3639,7 @@ list_sort_comparator
 loc_chunk
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 local_ts_iter
 local_ts_radix_tree
@@ -3926,7 +3928,6 @@ scram_state_enum
 script_error_callback_arg
 security_class_t
 sem_t
-sepgsql_context_info_t
 sequence_magic
 set_join_pathlist_hook_type
 set_rel_pathlist_hook_type
@@ -4151,6 +4152,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
@@ -4162,7 +4164,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.47.2

v11-0005-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From 737b215d4aa9b364c211c9023b89dfa082f253d7 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Aug 2024 13:25:19 +0900
Subject: [PATCH v11 5/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     | 49 +++++++++++++++++++++
 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, 207 insertions(+), 11 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index f0f19bb0b291..2b8298f19414 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -96,6 +96,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
@@ -186,6 +187,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 b9d7ab98c3e6..46d90892b103 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -176,6 +176,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;
@@ -1228,6 +1229,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;
@@ -2383,6 +2385,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 */
 
@@ -2612,6 +2615,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);
@@ -2716,6 +2720,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);
 
@@ -3372,6 +3379,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;
 
@@ -3534,6 +3544,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.
  */
@@ -3743,6 +3804,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
 	_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);
 
@@ -4267,6 +4329,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);
@@ -5006,6 +5070,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -5065,6 +5130,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 ce5ed1dd395d..cdfebcd34047 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)
 
@@ -322,6 +323,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 */
 
@@ -353,6 +355,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;
@@ -394,6 +397,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 30dfda8c3ffc..e0e8430515e0 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -133,6 +133,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
@@ -482,6 +483,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},
@@ -1107,6 +1109,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;
@@ -1223,6 +1226,7 @@ help(const char *progname)
 	printf(_("  --no-publications            do not dump publications\n"));
 	printf(_("  --no-security-labels         do not dump security label assignments\n"));
 	printf(_("  --no-subscriptions           do not dump subscriptions\n"));
+	printf(_("  --no-sequence-access-method  do not 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"));
@@ -13293,6 +13297,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;
@@ -17682,26 +17689,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);
@@ -17721,6 +17742,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);
@@ -17792,6 +17817,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);
 	}
@@ -17914,6 +17940,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 64a60a260928..f834cff3baf7 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -97,6 +97,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;
@@ -161,6 +162,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},
@@ -435,6 +437,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)
@@ -669,6 +673,7 @@ help(void)
 	printf(_("  --no-security-labels         do not dump security label assignments\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 c602272d7dbb..4907c494bcdf 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -67,6 +67,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;
@@ -116,6 +117,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},
@@ -368,6 +370,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;
@@ -494,6 +497,7 @@ usage(const char *progname)
 	printf(_("  --no-publications            do not restore publications\n"));
 	printf(_("  --no-security-labels         do not restore security labels\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(_("  --section=SECTION            restore named section (pre-data, data, or post-data)\n"));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index bc5d9222a208..888ac6d0f191 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -595,6 +595,13 @@ 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', 'postgres',
+		],
+	},
 	no_table_access_method => {
 		dump_cmd => [
 			'pg_dump', '--no-sync',
@@ -776,6 +783,7 @@ my %full_runs = (
 	no_large_objects => 1,
 	no_owner => 1,
 	no_privs => 1,
+	no_sequence_access_method => 1,
 	no_table_access_method => 1,
 	pg_dumpall_dbprivs => 1,
 	pg_dumpall_exclude => 1,
@@ -4601,6 +4609,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
@@ -4629,6 +4649,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 24fcc76d72c2..9f7dd6afe318 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1121,6 +1121,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 39d93c2c0e30..af095576f984 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -479,6 +479,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 b8b27e1719ee..922f69eb4abf 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -733,6 +733,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.47.2

v11-0006-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From 36b09ab06e77243a0000502ff9b64a6a8326d139 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v11 6/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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 336630ce417e..722882327b0f 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9403,6 +9403,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 66e6dccd4c9c..4c367bd5fe26 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -95,6 +95,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 7be25c58507f..cc390e613a34 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -257,6 +257,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..3067dc4d4df0 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>SEQUENCE</literal> and
+      <literal>INDEX</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 1e283f13d15c..52c6096e4ba2 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..a96170bfac03
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.47.2

v11-0007-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From 88941a9878ca9cc9e814ded82968ffeb3c48ce25 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 26 Apr 2024 15:09:00 +0900
Subject: [PATCH v11 7/7] snowflake: Add sequence AM based on it

This includes documentation and a basic implementation, though this is
in a very early stage.
---
 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            | 570 +++++++++++++++++++++++
 contrib/snowflake/snowflake.control      |   5 +
 contrib/snowflake/sql/snowflake.sql      |  29 ++
 13 files changed, 858 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 7c381949a533..7f55133bd2bd 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -167,6 +167,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 4c367bd5fe26..5247bee3224a 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -157,6 +157,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 952855d9b61b..26ec31734950 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -43,6 +43,7 @@ SUBDIRS = \
 		pg_walinspect	\
 		postgres_fdw	\
 		seg		\
+		snowflake	\
 		spi		\
 		tablefunc	\
 		tcn		\
diff --git a/contrib/meson.build b/contrib/meson.build
index 1ba73ebd67a3..9f11d5b00e44 100644
--- a/contrib/meson.build
+++ b/contrib/meson.build
@@ -58,6 +58,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..567669eea790
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2024, 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 += bloom
+
+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..be4c4039ecd0
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,570 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2024, 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/sequenceam.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 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(snowflake_magic));
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+	sm->magic = SNOWFLAKE_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 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, (Item) 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)
+{
+	Page		page;
+	ItemId		lp;
+	snowflake_magic *sm;
+	Form_snowflake_data	seq;
+
+	*buf = ReadBuffer(rel, 0);
+	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(*buf);
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+
+	if (sm->magic != SNOWFLAKE_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_snowflake_data) GETSTRUCT(seqdatatuple);
+
+	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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_snowflake_data seq;
+
+	seq = read_snowflake(rel, &buf, &seqdatatuple);
+	*last_value = seq->count;
+	*is_called = seq->is_called;
+	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.47.2

v11-0001-Remove-FormData_pg_sequence_data-from-init_param.patchtext/x-diff; charset=us-asciiDownload
From 934fba0f3f63d8aa49e3e77fe452f9f51dac4fc4 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:00:45 +0900
Subject: [PATCH v11 1/7] Remove FormData_pg_sequence_data from
 init_params()/sequence.c

init_params() sets up "last_value" and "is_called" for a sequence, based
on the sequence properties in pg_sequences.  This simplifies the logic
around log_cnt, which is reset to 0 when the metadata of a sequence is
expected to start from afresh when its properties are updated.
---
 src/backend/commands/sequence.c | 81 ++++++++++++++++++++-------------
 1 file changed, 49 insertions(+), 32 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 4b7c5113aabe..576c2ec502db 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -106,7 +106,9 @@ static Form_pg_sequence_data read_seq_tuple(Relation rel,
 static void init_params(ParseState *pstate, List *options, bool for_identity,
 						bool isInit,
 						Form_pg_sequence seqform,
-						Form_pg_sequence_data seqdataform,
+						int64 *last_value,
+						bool *reset_state,
+						bool *is_called,
 						bool *need_seq_rewrite,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
@@ -121,7 +123,9 @@ ObjectAddress
 DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 {
 	FormData_pg_sequence seqform;
-	FormData_pg_sequence_data seqdataform;
+	int64		last_value;
+	bool		reset_state;
+	bool		is_called;
 	bool		need_seq_rewrite;
 	List	   *owned_by;
 	CreateStmt *stmt = makeNode(CreateStmt);
@@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* Check and set all option values */
 	init_params(pstate, seq->options, seq->for_identity, true,
-				&seqform, &seqdataform,
+				&seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/*
@@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 		{
 			case SEQ_COL_LASTVAL:
 				coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
-				value[i - 1] = Int64GetDatumFast(seqdataform.last_value);
+				value[i - 1] = Int64GetDatumFast(last_value);
 				break;
 			case SEQ_COL_LOG:
 				coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
@@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	ObjectAddress address;
 	Relation	rel;
 	HeapTuple	seqtuple;
+	bool		reset_state = false;
+	bool		is_called;
+	int64		last_value;
 	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
@@ -481,12 +488,14 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	/* 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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
-				seqform, newdataform,
+				seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/* If needed, rewrite the sequence relation itself */
@@ -513,6 +522,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * 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);
 	}
 
@@ -1236,17 +1249,19 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 /*
  * init_params: process the options list of CREATE or ALTER SEQUENCE, and
  * store the values into appropriate fields of seqform, for changes that go
- * into the pg_sequence catalog, and fields of seqdataform for changes to the
- * sequence relation itself.  Set *need_seq_rewrite to true if we changed any
- * parameters that require rewriting the sequence's relation (interesting for
- * ALTER SEQUENCE).  Also set *owned_by to any OWNED BY option, or to NIL if
- * there is none.
+ * into the pg_sequence catalog, and fields for changes to the sequence
+ * relation itself (is_called, last_value or any state it may hold).  Set
+ * *need_seq_rewrite to true if we changed any parameters that require
+ * rewriting the sequence's relation (interesting for ALTER SEQUENCE).  Also
+ * set *owned_by to any OWNED BY option, or to NIL if there is none.  Set
+ * *reset_state if the internal state of the sequence needs to change on a
+ * follow-up nextval().
  *
  * If isInit is true, fill any unspecified options with default values;
  * otherwise, do not change existing options that aren't explicitly overridden.
  *
  * Note: we force a sequence rewrite whenever we change parameters that affect
- * generation of future sequence values, even if the seqdataform per se is not
+ * generation of future sequence values, even if the metadata per se is not
  * changed.  This allows ALTER SEQUENCE to behave transactionally.  Currently,
  * the only option that doesn't cause that is OWNED BY.  It's *necessary* for
  * ALTER SEQUENCE OWNED BY to not rewrite the sequence, because that would
@@ -1257,7 +1272,9 @@ static void
 init_params(ParseState *pstate, List *options, bool for_identity,
 			bool isInit,
 			Form_pg_sequence seqform,
-			Form_pg_sequence_data seqdataform,
+			int64 *last_value,
+			bool *reset_state,
+			bool *is_called,
 			bool *need_seq_rewrite,
 			List **owned_by)
 {
@@ -1363,11 +1380,11 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	}
 
 	/*
-	 * We must reset log_cnt when isInit or when changing any parameters that
-	 * would affect future nextval allocations.
+	 * We must reset the state when isInit or when changing any parameters
+	 * that would affect future nextval allocations.
 	 */
 	if (isInit)
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 
 	/* AS type */
 	if (as_type != NULL)
@@ -1416,7 +1433,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("INCREMENT must not be zero")));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1428,7 +1445,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	{
 		seqform->seqcycle = boolVal(is_cycled->arg);
 		Assert(BoolIsValid(seqform->seqcycle));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1439,7 +1456,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (max_value != NULL && max_value->arg)
 	{
 		seqform->seqmax = defGetInt64(max_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || max_value != NULL || reset_max_value)
 	{
@@ -1455,7 +1472,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmax = -1;	/* descending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate maximum value.  No need to check INT8 as seqmax is an int64 */
@@ -1471,7 +1488,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (min_value != NULL && min_value->arg)
 	{
 		seqform->seqmin = defGetInt64(min_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || min_value != NULL || reset_min_value)
 	{
@@ -1487,7 +1504,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmin = 1;	/* ascending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate minimum value.  No need to check INT8 as seqmin is an int64 */
@@ -1538,30 +1555,30 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (restart_value != NULL)
 	{
 		if (restart_value->arg != NULL)
-			seqdataform->last_value = defGetInt64(restart_value);
+			*last_value = defGetInt64(restart_value);
 		else
-			seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
-		seqdataform->log_cnt = 0;
+			*last_value = seqform->seqstart;
+		*is_called = false;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-		seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
+		*last_value = seqform->seqstart;
+		*is_called = false;
 	}
 
 	/* crosscheck RESTART (or current value, if changing MIN/MAX) */
-	if (seqdataform->last_value < seqform->seqmin)
+	if (*last_value < seqform->seqmin)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be less than MINVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmin)));
-	if (seqdataform->last_value > seqform->seqmax)
+	if (*last_value > seqform->seqmax)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be greater than MAXVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmax)));
 
 	/* CACHE */
@@ -1573,7 +1590,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("CACHE (%lld) must be greater than zero",
 							(long long) seqform->seqcache)));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-- 
2.47.2

#30Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#29)
7 attachment(s)
Re: Sequence Access Methods, round two

On Tue, Feb 18, 2025 at 02:11:04PM +0900, Michael Paquier wrote:

Conflicts caused by ed5e5f071033 in the third patch in sequence.c with
adjustments in the new files seqlocalam.c were required, hence here is
a rebased version labelled v11.

Rebased.
--
Michael

Attachments:

v12-0001-Remove-FormData_pg_sequence_data-from-init_param.patchtext/x-diff; charset=us-asciiDownload
From 78c467e5bc477b62d70b470c5f5fcc77c7752a08 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:00:45 +0900
Subject: [PATCH v12 1/7] Remove FormData_pg_sequence_data from
 init_params()/sequence.c

init_params() sets up "last_value" and "is_called" for a sequence, based
on the sequence properties in pg_sequences.  This simplifies the logic
around log_cnt, which is reset to 0 when the metadata of a sequence is
expected to start from afresh when its properties are updated.
---
 src/backend/commands/sequence.c | 81 ++++++++++++++++++++-------------
 1 file changed, 49 insertions(+), 32 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 4b7c5113aabe..576c2ec502db 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -106,7 +106,9 @@ static Form_pg_sequence_data read_seq_tuple(Relation rel,
 static void init_params(ParseState *pstate, List *options, bool for_identity,
 						bool isInit,
 						Form_pg_sequence seqform,
-						Form_pg_sequence_data seqdataform,
+						int64 *last_value,
+						bool *reset_state,
+						bool *is_called,
 						bool *need_seq_rewrite,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
@@ -121,7 +123,9 @@ ObjectAddress
 DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 {
 	FormData_pg_sequence seqform;
-	FormData_pg_sequence_data seqdataform;
+	int64		last_value;
+	bool		reset_state;
+	bool		is_called;
 	bool		need_seq_rewrite;
 	List	   *owned_by;
 	CreateStmt *stmt = makeNode(CreateStmt);
@@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* Check and set all option values */
 	init_params(pstate, seq->options, seq->for_identity, true,
-				&seqform, &seqdataform,
+				&seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/*
@@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 		{
 			case SEQ_COL_LASTVAL:
 				coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
-				value[i - 1] = Int64GetDatumFast(seqdataform.last_value);
+				value[i - 1] = Int64GetDatumFast(last_value);
 				break;
 			case SEQ_COL_LOG:
 				coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
@@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	ObjectAddress address;
 	Relation	rel;
 	HeapTuple	seqtuple;
+	bool		reset_state = false;
+	bool		is_called;
+	int64		last_value;
 	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
@@ -481,12 +488,14 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	/* 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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
-				seqform, newdataform,
+				seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/* If needed, rewrite the sequence relation itself */
@@ -513,6 +522,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * 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);
 	}
 
@@ -1236,17 +1249,19 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 /*
  * init_params: process the options list of CREATE or ALTER SEQUENCE, and
  * store the values into appropriate fields of seqform, for changes that go
- * into the pg_sequence catalog, and fields of seqdataform for changes to the
- * sequence relation itself.  Set *need_seq_rewrite to true if we changed any
- * parameters that require rewriting the sequence's relation (interesting for
- * ALTER SEQUENCE).  Also set *owned_by to any OWNED BY option, or to NIL if
- * there is none.
+ * into the pg_sequence catalog, and fields for changes to the sequence
+ * relation itself (is_called, last_value or any state it may hold).  Set
+ * *need_seq_rewrite to true if we changed any parameters that require
+ * rewriting the sequence's relation (interesting for ALTER SEQUENCE).  Also
+ * set *owned_by to any OWNED BY option, or to NIL if there is none.  Set
+ * *reset_state if the internal state of the sequence needs to change on a
+ * follow-up nextval().
  *
  * If isInit is true, fill any unspecified options with default values;
  * otherwise, do not change existing options that aren't explicitly overridden.
  *
  * Note: we force a sequence rewrite whenever we change parameters that affect
- * generation of future sequence values, even if the seqdataform per se is not
+ * generation of future sequence values, even if the metadata per se is not
  * changed.  This allows ALTER SEQUENCE to behave transactionally.  Currently,
  * the only option that doesn't cause that is OWNED BY.  It's *necessary* for
  * ALTER SEQUENCE OWNED BY to not rewrite the sequence, because that would
@@ -1257,7 +1272,9 @@ static void
 init_params(ParseState *pstate, List *options, bool for_identity,
 			bool isInit,
 			Form_pg_sequence seqform,
-			Form_pg_sequence_data seqdataform,
+			int64 *last_value,
+			bool *reset_state,
+			bool *is_called,
 			bool *need_seq_rewrite,
 			List **owned_by)
 {
@@ -1363,11 +1380,11 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	}
 
 	/*
-	 * We must reset log_cnt when isInit or when changing any parameters that
-	 * would affect future nextval allocations.
+	 * We must reset the state when isInit or when changing any parameters
+	 * that would affect future nextval allocations.
 	 */
 	if (isInit)
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 
 	/* AS type */
 	if (as_type != NULL)
@@ -1416,7 +1433,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("INCREMENT must not be zero")));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1428,7 +1445,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	{
 		seqform->seqcycle = boolVal(is_cycled->arg);
 		Assert(BoolIsValid(seqform->seqcycle));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1439,7 +1456,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (max_value != NULL && max_value->arg)
 	{
 		seqform->seqmax = defGetInt64(max_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || max_value != NULL || reset_max_value)
 	{
@@ -1455,7 +1472,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmax = -1;	/* descending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate maximum value.  No need to check INT8 as seqmax is an int64 */
@@ -1471,7 +1488,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (min_value != NULL && min_value->arg)
 	{
 		seqform->seqmin = defGetInt64(min_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || min_value != NULL || reset_min_value)
 	{
@@ -1487,7 +1504,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmin = 1;	/* ascending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate minimum value.  No need to check INT8 as seqmin is an int64 */
@@ -1538,30 +1555,30 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (restart_value != NULL)
 	{
 		if (restart_value->arg != NULL)
-			seqdataform->last_value = defGetInt64(restart_value);
+			*last_value = defGetInt64(restart_value);
 		else
-			seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
-		seqdataform->log_cnt = 0;
+			*last_value = seqform->seqstart;
+		*is_called = false;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-		seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
+		*last_value = seqform->seqstart;
+		*is_called = false;
 	}
 
 	/* crosscheck RESTART (or current value, if changing MIN/MAX) */
-	if (seqdataform->last_value < seqform->seqmin)
+	if (*last_value < seqform->seqmin)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be less than MINVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmin)));
-	if (seqdataform->last_value > seqform->seqmax)
+	if (*last_value > seqform->seqmax)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be greater than MAXVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmax)));
 
 	/* CACHE */
@@ -1573,7 +1590,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("CACHE (%lld) must be greater than zero",
 							(long long) seqform->seqcache)));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-- 
2.47.2

v12-0002-Integrate-addition-of-attributes-for-sequences-w.patchtext/x-diff; charset=us-asciiDownload
From 9be8a972a8ad6c0ad0be6869086ee745b381047e Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v12 2/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               | 29 +++++++++++++++++--
 src/backend/commands/tablecmds.c              | 10 +++++++
 src/backend/tcop/utility.c                    |  4 +++
 .../test_ddl_deparse/expected/alter_table.out | 10 +++++--
 .../expected/create_sequence_1.out            |  5 +++-
 .../expected/create_table.out                 | 15 ++++++++--
 .../test_ddl_deparse/test_ddl_deparse.c       |  3 ++
 8 files changed, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 0b208f51bdd0..e5848e737149 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2402,6 +2402,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 576c2ec502db..a902c6ce6f09 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -136,6 +136,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;
@@ -174,7 +177,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;
@@ -198,7 +200,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;
@@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 ce7d115667eb..58dc70aad221 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4616,6 +4616,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 */
@@ -4911,6 +4912,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,
@@ -5343,6 +5351,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,
@@ -6502,6 +6511,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 25fe3d580166..0629dc5387c0 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 50d0354a3417..ed31059ef584 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
@@ -70,7 +73,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_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
index 5837ea484e40..310ce5a6baf5 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.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 193669f2bc1e..254fdf90c79a 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.47.2

v12-0003-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From 6e374358494d3f5fd6bddec67255eb4fadf53d42 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 25 Apr 2024 16:49:47 +0900
Subject: [PATCH v12 3/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.
- seqlocalxlog.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/seqlocalam.h               |  58 ++
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 .../rmgrdesc/{seqdesc.c => seqlocaldesc.c}    |  20 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/meson.build       |   2 +
 src/backend/access/sequence/seqlocalam.c      | 634 ++++++++++++++++++
 src/backend/access/sequence/seqlocalxlog.c    |  82 +++
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 634 +-----------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 14 files changed, 822 insertions(+), 622 deletions(-)
 create mode 100644 src/include/access/seqlocalam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%)
 create mode 100644 src/backend/access/sequence/seqlocalam.c
 create mode 100644 src/backend/access/sequence/seqlocalxlog.c

diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 8e7fc9db8778..b942b25350bc 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/seqlocalam.h b/src/include/access/seqlocalam.h
new file mode 100644
index 000000000000..225fb9a2cbeb
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2024, 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 "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_SEQ_LOCAL_LOG			0x00
+
+typedef struct xl_seq_local_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_seq_local_rec;
+
+/*
+ * 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;
+
+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);
+
+/* 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);
+extern void seq_local_change_persistence(Relation rel,
+										 char newrelpersistence);
+
+#endif							/* SEQLOCALAM_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 96c98e800c22..6b3b3e1e44c1 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 0d289d77fcf7..6ffbcb2c4735 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.c
  *
  * Portions Copyright (c) 1996-2025, 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.h"
+#include "access/seqlocalam.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..a15ceec1c0a0 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 ec9ab9b7e9db..8bc0e95e68c0 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2025, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'seqlocalam.c',
+  'seqlocalxlog.c',
   'sequence.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
new file mode 100644
index 000000000000..bc735ebb0838
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -0,0 +1,634 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.c
+ *	  Local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, 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/multixact.h"
+#include "access/seqlocalam.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"
+
+
+/*
+ * 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
+
+/* 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, (Item) 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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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 "heap";
+}
+
+/*
+ * 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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_seq_local_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	*last_value = seq->last_value;
+	*is_called = seq->is_called;
+
+	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/sequence/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c
new file mode 100644
index 000000000000..db0ad969dbf2
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalxlog.c
@@ -0,0 +1,82 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalxlog.c
+ *	  WAL replay logic for local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/access/sequence/seqlocalxlog.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/seqlocalam.h"
+#include "access/xlogutils.h"
+#include "storage/block.h"
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+seq_local_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+seq_local_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record);
+	seq_local_magic *sm;
+
+	if (info != XLOG_SEQ_LOCAL_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = (Page) BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	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_local_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec);
+
+	if (PageAddItem(localpage, (Item) item, itemsz,
+					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "seq_local_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 1b7499726eb0..cc92268937b1 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index a902c6ce6f09..b7ed47d92801 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -50,23 +51,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -96,13 +80,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,
@@ -134,14 +114,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
@@ -174,35 +148,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;
@@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	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)
@@ -292,10 +222,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;
@@ -306,7 +232,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))
@@ -315,40 +240,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 */
@@ -357,106 +250,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, (Item) 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
  *
@@ -468,10 +261,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;
@@ -480,7 +270,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	bool		reset_state = false;
 	bool		is_called;
 	int64		last_value;
-	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
 	relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -507,16 +296,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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -526,32 +307,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 */
@@ -580,8 +339,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
@@ -596,10 +353,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);
 }
@@ -662,24 +416,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);
@@ -724,105 +469,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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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;
@@ -832,69 +481,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;
@@ -984,9 +570,6 @@ do_setval(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,
@@ -1020,9 +603,6 @@ do_setval(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),
@@ -1044,37 +624,8 @@ do_setval(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);
 }
@@ -1215,62 +766,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
@@ -1855,16 +1350,13 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		Buffer		buf;
-		HeapTupleData seqtuple;
-		Form_pg_sequence_data seq;
+		bool		is_called;
+		int64		last_value;
 
-		seq = read_seq_tuple(seqrel, &buf, &seqtuple);
+		seq_local_get_state(seqrel, &last_value, &is_called);
 
-		values[0] = Int64GetDatum(seq->last_value);
-		values[1] = BoolGetDatum(seq->is_called);
-
-		UnlockReleaseBuffer(buf);
+		values[0] = Int64GetDatum(last_value);
+		values[1] = BoolGetDatum(is_called);
 	}
 	else
 		memset(isnull, true, sizeof(isnull));
@@ -1908,17 +1400,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);
 	}
+
 	sequence_close(seqrel, NoLock);
 
 	if (is_called)
@@ -1927,57 +1411,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
-
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = (Page) BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, (Item) item, itemsz,
-					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1992,14 +1425,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c767e..8d1195de2637 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/seqlocaldesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index 6b8c17bb4c4e..2fccc7a4e2bd 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index 5c8fea275bb5..56a6e93a9a77 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.47.2

v12-0004-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From 706db43ae51162c629594ee47059c3426bbb5c09 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 4 Dec 2024 08:09:46 +0900
Subject: [PATCH v12 4/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               |  15 --
 src/include/access/sequenceam.h               | 181 ++++++++++++++++++
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/seqlocalam.c      |  41 +++-
 src/backend/access/sequence/sequence.c        |   3 +-
 src/backend/access/sequence/sequenceamapi.c   | 145 ++++++++++++++
 src/backend/catalog/heap.c                    |   6 +-
 src/backend/commands/amcmds.c                 |  16 ++
 src/backend/commands/sequence.c               |  23 +--
 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_tables.c           |  12 ++
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/bin/psql/describe.c                       |   2 +
 src/bin/psql/tab-complete.in.c                |   4 +-
 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 +-
 40 files changed, 695 insertions(+), 182 deletions(-)
 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
index 225fb9a2cbeb..21936511ac2b 100644
--- a/src/include/access/seqlocalam.h
+++ b/src/include/access/seqlocalam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_SEQ_LOCAL_LOG			0x00
@@ -41,18 +40,4 @@ 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);
 
-/* 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);
-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..ac48c8b468be
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,181 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2024, 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.
+	 */
+	void		(*get_state) (Relation rel, int64 *last_value, bool *is_called);
+
+	/*
+	 * 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)
+{
+	rel->rd_sequenceam->get_state(rel, last_value, is_called);
+}
+
+/*
+ * 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 26d15928a155..8f076fcec958 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 6e98a0930c27..080bea5031a9 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 f0d612ca4877..f217c8d8fb07 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -228,6 +228,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)
+
 extern int	errdetail_relkind_not_supported(char relkind);
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index af9546de23df..3e9cddc4bdd7 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',
@@ -7803,6 +7809,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 6dca77e0a22f..c42531b25533 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -627,6 +627,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 6d9348bac804..eb2e344a184f 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -145,6 +145,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 9ac0b67683d3..7693e9941fc9 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index d1ca24dd32f0..b1c4155c9a91 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 e5848e737149..e5bdcf3db9a8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3207,6 +3207,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 951451a9765f..a346e408ef2b 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -53,6 +53,8 @@ extern bool check_debug_io_direct(char **newval, void **extra, GucSource source)
 extern void assign_debug_io_direct(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 db3e504c3d2d..f2155c1195d8 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 a15ceec1c0a0..62006165a15f 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = seqlocalam.o seqlocalxlog.o sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 8bc0e95e68c0..d82af34d538c 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -4,4 +4,5 @@ backend_sources += files(
   'seqlocalam.c',
   'seqlocalxlog.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
index bc735ebb0838..946c7f87a686 100644
--- a/src/backend/access/sequence/seqlocalam.c
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -17,6 +17,7 @@
 
 #include "access/multixact.h"
 #include "access/seqlocalam.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -24,6 +25,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -230,10 +232,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;
@@ -417,7 +419,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 "heap";
@@ -432,7 +434,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];
@@ -499,7 +501,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;
@@ -547,7 +549,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;
@@ -600,7 +602,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)
 {
 	Buffer		buf;
@@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called)
  *
  * Persistence change for the local sequence Relation.
  */
-void
+static void
 seq_local_change_persistence(Relation rel, char newrelpersistence)
 {
 	Buffer		buf;
@@ -632,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 8b5303553702..f30317d1feae 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..dd1a60d827a4
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2024, 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/xact.h"
+#include "access/sequenceam.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 956f196fc95c..4d06010b9a88 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1477,9 +1477,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 58ed9d216cc0..e44633d13b60 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 b7ed47d92801..e88ace9b4a4d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -152,6 +152,7 @@ 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;
@@ -173,7 +174,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)
@@ -241,7 +242,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 */
@@ -297,7 +298,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
 
 	/* Read sequence data */
-	seq_local_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -310,7 +311,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 */
@@ -353,7 +354,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);
 }
@@ -470,8 +471,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;
@@ -625,7 +626,7 @@ do_setval(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);
 }
@@ -1353,7 +1354,7 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		bool		is_called;
 		int64		last_value;
 
-		seq_local_get_state(seqrel, &last_value, &is_called);
+		sequence_get_state(seqrel, &last_value, &is_called);
 
 		values[0] = Int64GetDatum(last_value);
 		values[1] = BoolGetDatum(is_called);
@@ -1400,7 +1401,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		seq_local_get_state(seqrel, &result, &is_called);
+		sequence_get_state(seqrel, &result, &is_called);
 	}
 
 	sequence_close(seqrel, NoLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 58dc70aad221..cd3d18903855 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"
@@ -995,14 +996,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)
 	{
@@ -1015,6 +1020,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 1a657f7e0aea..162ee16dbf6d 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 7d99c9355c6a..4144d2dd9b31 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -381,6 +381,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
@@ -4919,23 +4920,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;
@@ -4972,6 +4976,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); }
 		;
@@ -5975,6 +5984,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 abbe1bb45a30..571faec28e98 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"
@@ -518,6 +519,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 317a1f2b282f..68f160dda7d3 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 398114373e9f..c10e77372071 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);
@@ -1196,8 +1199,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)
 	{
@@ -1206,6 +1208,8 @@ retry:
 		 * inherit.
 		 */
 	}
+	else if (RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind))
+		RelationInitSequenceAccessMethod(relation);
 	else
 		Assert(relation->rd_rel->relam == InvalidOid);
 
@@ -1802,17 +1806,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.
@@ -1843,6 +1839,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
  *
@@ -3659,14 +3698,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
@@ -4290,13 +4332,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);
@@ -6348,8 +6398,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);
@@ -6361,6 +6413,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_tables.c b/src/backend/utils/misc/guc_tables.c
index 690bf96ef030..ae918f69bb2c 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -30,6 +30,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"
@@ -4257,6 +4258,17 @@ struct config_string ConfigureNamesString[] =
 		check_default_table_access_method, NULL, NULL
 	},
 
+	{
+		{"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets the default sequence access method for new sequences."),
+			NULL,
+			GUC_IS_NAME
+		},
+		&default_sequence_access_method,
+		DEFAULT_SEQUENCE_ACCESS_METHOD,
+		check_default_sequence_access_method, NULL, NULL
+	},
+
 	{
 		{"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default tablespace to create tables and indexes in."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index e771d87da1fb..fbc3f7577c33 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -728,6 +728,7 @@ autovacuum_worker_slots = 16	# autovacuum worker slots to allocate
 					#   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 e6cf468ac9e9..b50a3f992eff 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 8432be641ac0..4fa37fd4d822 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2561,7 +2561,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");
@@ -3601,7 +3601,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 b673642ad1d7..3dedeff17201 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1940,6 +1940,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 6543e90de758..c2a2c1fe0bc2 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5126,31 +5126,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
@@ -5175,32 +5177,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 8eff3d10d279..622f360d5b3c 100644
--- a/src/test/regress/expected/type_sanity.out
+++ b/src/test/regress/expected/type_sanity.out
@@ -507,21 +507,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 2fb3a8528781..e60eeab33c02 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 303f90955d15..aade05c0e172 100644
--- a/src/test/regress/sql/type_sanity.sql
+++ b/src/test/regress/sql/type_sanity.sql
@@ -364,18 +364,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 e3e09a2207e9..8a06eb211af1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2611,6 +2611,7 @@ SeqScanState
 SeqTable
 SeqTableData
 SeqType
+SequenceAmRoutine
 SequenceItem
 SerCommitSeqNo
 SerialControl
@@ -3652,6 +3653,7 @@ list_sort_comparator
 loc_chunk
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 local_ts_iter
 local_ts_radix_tree
@@ -3940,7 +3942,6 @@ scram_state_enum
 script_error_callback_arg
 security_class_t
 sem_t
-sepgsql_context_info_t
 sequence_magic
 set_join_pathlist_hook_type
 set_rel_pathlist_hook_type
@@ -4165,6 +4166,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
@@ -4176,7 +4178,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.47.2

v12-0005-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From 678d7dbe7e56b4e8c0a7de1a18cb5b7f19e1268b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Aug 2024 13:25:19 +0900
Subject: [PATCH v12 5/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     | 49 +++++++++++++++++++++
 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, 207 insertions(+), 11 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 350cf659c410..8ba0ec05f8b7 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -96,6 +96,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
@@ -187,6 +188,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 632077113a4a..e264cf721a2c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -181,6 +181,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;
@@ -1240,6 +1241,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;
@@ -2395,6 +2397,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 */
 
@@ -2624,6 +2627,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);
@@ -2728,6 +2732,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);
 
@@ -3399,6 +3406,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;
 
@@ -3561,6 +3571,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.
  */
@@ -3770,6 +3831,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);
 
@@ -4288,6 +4350,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);
@@ -5027,6 +5091,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -5086,6 +5151,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 a2064f471ede..5a091c03ce11 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 */
 
@@ -354,6 +356,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;
@@ -395,6 +398,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 afd792871777..7c994fa04db4 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -133,6 +133,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
@@ -486,6 +487,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},
@@ -1144,6 +1146,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;
@@ -1263,6 +1266,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 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"));
@@ -13668,6 +13672,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;
@@ -18057,26 +18064,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);
@@ -18096,6 +18117,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);
@@ -18167,6 +18192,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);
 	}
@@ -18289,6 +18315,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 e08672425263..2a33d158379a 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -97,6 +97,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;
@@ -165,6 +166,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},
@@ -443,6 +445,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)
@@ -688,6 +692,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 13e4dc507e04..bbde36049a56 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -69,6 +69,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;
@@ -120,6 +121,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},
@@ -381,6 +383,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;
@@ -510,6 +513,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(_("  --section=SECTION            restore named section (pre-data, data, or post-data)\n"));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 3945e4f0e2aa..3c922920a625 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -595,6 +595,13 @@ 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', 'postgres',
+		],
+	},
 	no_table_access_method => {
 		dump_cmd => [
 			'pg_dump', '--no-sync',
@@ -805,6 +812,7 @@ my %full_runs = (
 	no_owner => 1,
 	no_privs => 1,
 	no_statistics => 1,
+	no_sequence_access_method => 1,
 	no_table_access_method => 1,
 	pg_dumpall_dbprivs => 1,
 	pg_dumpall_exclude => 1,
@@ -4659,6 +4667,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
@@ -4687,6 +4707,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 1975054d7bfb..bb1de62aaba1 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1164,6 +1164,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 c2fa5be95193..79acdac16bd7 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -516,6 +516,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 199ea3345f30..81957b39eebe 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -773,6 +773,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.47.2

v12-0006-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From fca49a46d5973e6379ba52b1bc911308ff1da600 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v12 6/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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 a83545761085..dfdac5271f2f 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9447,6 +9447,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 25fb99cee694..bc34378dd68b 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -95,6 +95,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 af476c82fcc1..9f369cc89930 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..3067dc4d4df0 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>SEQUENCE</literal> and
+      <literal>INDEX</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 1e283f13d15c..52c6096e4ba2 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..a96170bfac03
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.47.2

v12-0007-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From c0324525c4bfa217875d4f52e11fa5c802c5c620 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 26 Apr 2024 15:09:00 +0900
Subject: [PATCH v12 7/7] snowflake: Add sequence AM based on it

This includes documentation and a basic implementation, though this is
in a very early stage.
---
 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            | 570 +++++++++++++++++++++++
 contrib/snowflake/snowflake.control      |   5 +
 contrib/snowflake/sql/snowflake.sql      |  29 ++
 13 files changed, 858 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 7c381949a533..7f55133bd2bd 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -167,6 +167,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 bc34378dd68b..af5916de5a41 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -158,6 +158,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 952855d9b61b..26ec31734950 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -43,6 +43,7 @@ SUBDIRS = \
 		pg_walinspect	\
 		postgres_fdw	\
 		seg		\
+		snowflake	\
 		spi		\
 		tablefunc	\
 		tcn		\
diff --git a/contrib/meson.build b/contrib/meson.build
index 1ba73ebd67a3..9f11d5b00e44 100644
--- a/contrib/meson.build
+++ b/contrib/meson.build
@@ -58,6 +58,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..567669eea790
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2024, 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 += bloom
+
+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..be4c4039ecd0
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,570 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2024, 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/sequenceam.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 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(snowflake_magic));
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+	sm->magic = SNOWFLAKE_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 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, (Item) 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)
+{
+	Page		page;
+	ItemId		lp;
+	snowflake_magic *sm;
+	Form_snowflake_data	seq;
+
+	*buf = ReadBuffer(rel, 0);
+	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(*buf);
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+
+	if (sm->magic != SNOWFLAKE_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_snowflake_data) GETSTRUCT(seqdatatuple);
+
+	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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_snowflake_data seq;
+
+	seq = read_snowflake(rel, &buf, &seqdatatuple);
+	*last_value = seq->count;
+	*is_called = seq->is_called;
+	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.47.2

#31Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#30)
7 attachment(s)
Re: Sequence Access Methods, round two

On Tue, Feb 25, 2025 at 04:25:37PM +0900, Michael Paquier wrote:

Rebased.

Conflict in pg_class.h. Rebased.
--
Michael

Attachments:

v13-0001-Remove-FormData_pg_sequence_data-from-init_param.patchtext/x-diff; charset=us-asciiDownload
From 2e87a746a0c403505e200290653caaf706622fbd Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:00:45 +0900
Subject: [PATCH v13 1/7] Remove FormData_pg_sequence_data from
 init_params()/sequence.c

init_params() sets up "last_value" and "is_called" for a sequence, based
on the sequence properties in pg_sequences.  This simplifies the logic
around log_cnt, which is reset to 0 when the metadata of a sequence is
expected to start from afresh when its properties are updated.
---
 src/backend/commands/sequence.c | 81 ++++++++++++++++++++-------------
 1 file changed, 49 insertions(+), 32 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 4b7c5113aabe..576c2ec502db 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -106,7 +106,9 @@ static Form_pg_sequence_data read_seq_tuple(Relation rel,
 static void init_params(ParseState *pstate, List *options, bool for_identity,
 						bool isInit,
 						Form_pg_sequence seqform,
-						Form_pg_sequence_data seqdataform,
+						int64 *last_value,
+						bool *reset_state,
+						bool *is_called,
 						bool *need_seq_rewrite,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
@@ -121,7 +123,9 @@ ObjectAddress
 DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 {
 	FormData_pg_sequence seqform;
-	FormData_pg_sequence_data seqdataform;
+	int64		last_value;
+	bool		reset_state;
+	bool		is_called;
 	bool		need_seq_rewrite;
 	List	   *owned_by;
 	CreateStmt *stmt = makeNode(CreateStmt);
@@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* Check and set all option values */
 	init_params(pstate, seq->options, seq->for_identity, true,
-				&seqform, &seqdataform,
+				&seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/*
@@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 		{
 			case SEQ_COL_LASTVAL:
 				coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
-				value[i - 1] = Int64GetDatumFast(seqdataform.last_value);
+				value[i - 1] = Int64GetDatumFast(last_value);
 				break;
 			case SEQ_COL_LOG:
 				coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
@@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	ObjectAddress address;
 	Relation	rel;
 	HeapTuple	seqtuple;
+	bool		reset_state = false;
+	bool		is_called;
+	int64		last_value;
 	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
@@ -481,12 +488,14 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	/* 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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
-				seqform, newdataform,
+				seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/* If needed, rewrite the sequence relation itself */
@@ -513,6 +522,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * 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);
 	}
 
@@ -1236,17 +1249,19 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 /*
  * init_params: process the options list of CREATE or ALTER SEQUENCE, and
  * store the values into appropriate fields of seqform, for changes that go
- * into the pg_sequence catalog, and fields of seqdataform for changes to the
- * sequence relation itself.  Set *need_seq_rewrite to true if we changed any
- * parameters that require rewriting the sequence's relation (interesting for
- * ALTER SEQUENCE).  Also set *owned_by to any OWNED BY option, or to NIL if
- * there is none.
+ * into the pg_sequence catalog, and fields for changes to the sequence
+ * relation itself (is_called, last_value or any state it may hold).  Set
+ * *need_seq_rewrite to true if we changed any parameters that require
+ * rewriting the sequence's relation (interesting for ALTER SEQUENCE).  Also
+ * set *owned_by to any OWNED BY option, or to NIL if there is none.  Set
+ * *reset_state if the internal state of the sequence needs to change on a
+ * follow-up nextval().
  *
  * If isInit is true, fill any unspecified options with default values;
  * otherwise, do not change existing options that aren't explicitly overridden.
  *
  * Note: we force a sequence rewrite whenever we change parameters that affect
- * generation of future sequence values, even if the seqdataform per se is not
+ * generation of future sequence values, even if the metadata per se is not
  * changed.  This allows ALTER SEQUENCE to behave transactionally.  Currently,
  * the only option that doesn't cause that is OWNED BY.  It's *necessary* for
  * ALTER SEQUENCE OWNED BY to not rewrite the sequence, because that would
@@ -1257,7 +1272,9 @@ static void
 init_params(ParseState *pstate, List *options, bool for_identity,
 			bool isInit,
 			Form_pg_sequence seqform,
-			Form_pg_sequence_data seqdataform,
+			int64 *last_value,
+			bool *reset_state,
+			bool *is_called,
 			bool *need_seq_rewrite,
 			List **owned_by)
 {
@@ -1363,11 +1380,11 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	}
 
 	/*
-	 * We must reset log_cnt when isInit or when changing any parameters that
-	 * would affect future nextval allocations.
+	 * We must reset the state when isInit or when changing any parameters
+	 * that would affect future nextval allocations.
 	 */
 	if (isInit)
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 
 	/* AS type */
 	if (as_type != NULL)
@@ -1416,7 +1433,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("INCREMENT must not be zero")));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1428,7 +1445,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	{
 		seqform->seqcycle = boolVal(is_cycled->arg);
 		Assert(BoolIsValid(seqform->seqcycle));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1439,7 +1456,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (max_value != NULL && max_value->arg)
 	{
 		seqform->seqmax = defGetInt64(max_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || max_value != NULL || reset_max_value)
 	{
@@ -1455,7 +1472,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmax = -1;	/* descending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate maximum value.  No need to check INT8 as seqmax is an int64 */
@@ -1471,7 +1488,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (min_value != NULL && min_value->arg)
 	{
 		seqform->seqmin = defGetInt64(min_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || min_value != NULL || reset_min_value)
 	{
@@ -1487,7 +1504,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmin = 1;	/* ascending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate minimum value.  No need to check INT8 as seqmin is an int64 */
@@ -1538,30 +1555,30 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (restart_value != NULL)
 	{
 		if (restart_value->arg != NULL)
-			seqdataform->last_value = defGetInt64(restart_value);
+			*last_value = defGetInt64(restart_value);
 		else
-			seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
-		seqdataform->log_cnt = 0;
+			*last_value = seqform->seqstart;
+		*is_called = false;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-		seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
+		*last_value = seqform->seqstart;
+		*is_called = false;
 	}
 
 	/* crosscheck RESTART (or current value, if changing MIN/MAX) */
-	if (seqdataform->last_value < seqform->seqmin)
+	if (*last_value < seqform->seqmin)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be less than MINVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmin)));
-	if (seqdataform->last_value > seqform->seqmax)
+	if (*last_value > seqform->seqmax)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%lld) cannot be greater than MAXVALUE (%lld)",
-						(long long) seqdataform->last_value,
+						(long long) *last_value,
 						(long long) seqform->seqmax)));
 
 	/* CACHE */
@@ -1573,7 +1590,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("CACHE (%lld) must be greater than zero",
 							(long long) seqform->seqcache)));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-- 
2.49.0

v13-0002-Integrate-addition-of-attributes-for-sequences-w.patchtext/x-diff; charset=us-asciiDownload
From 998b1bdc12edf13e23cb1ee366833f191834b3d7 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v13 2/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               | 29 +++++++++++++++++--
 src/backend/commands/tablecmds.c              | 10 +++++++
 src/backend/tcop/utility.c                    |  4 +++
 .../test_ddl_deparse/expected/alter_table.out | 10 +++++--
 .../expected/create_sequence_1.out            |  5 +++-
 .../expected/create_table.out                 | 15 ++++++++--
 .../test_ddl_deparse/test_ddl_deparse.c       |  3 ++
 8 files changed, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index df331b1c0d99..d637a2381c12 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2407,6 +2407,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 576c2ec502db..a902c6ce6f09 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -136,6 +136,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;
@@ -174,7 +177,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;
@@ -198,7 +200,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;
@@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 afb250076136..04ee8ed38be1 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4626,6 +4626,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 */
@@ -4921,6 +4922,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,
@@ -5353,6 +5361,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,
@@ -6510,6 +6519,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 25fe3d580166..0629dc5387c0 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 50d0354a3417..ed31059ef584 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
@@ -70,7 +73,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_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
index 5837ea484e40..310ce5a6baf5 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.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 193669f2bc1e..254fdf90c79a 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.49.0

v13-0003-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From b293014a8726fe708125ef63defaaea9d5f5fbd8 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 25 Apr 2024 16:49:47 +0900
Subject: [PATCH v13 3/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.
- seqlocalxlog.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/seqlocalam.h               |  58 ++
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 .../rmgrdesc/{seqdesc.c => seqlocaldesc.c}    |  20 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/meson.build       |   2 +
 src/backend/access/sequence/seqlocalam.c      | 634 ++++++++++++++++++
 src/backend/access/sequence/seqlocalxlog.c    |  82 +++
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 634 +-----------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 14 files changed, 822 insertions(+), 622 deletions(-)
 create mode 100644 src/include/access/seqlocalam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%)
 create mode 100644 src/backend/access/sequence/seqlocalam.c
 create mode 100644 src/backend/access/sequence/seqlocalxlog.c

diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 8e7fc9db8778..b942b25350bc 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/seqlocalam.h b/src/include/access/seqlocalam.h
new file mode 100644
index 000000000000..225fb9a2cbeb
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2024, 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 "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_SEQ_LOCAL_LOG			0x00
+
+typedef struct xl_seq_local_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_seq_local_rec;
+
+/*
+ * 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;
+
+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);
+
+/* 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);
+extern void seq_local_change_persistence(Relation rel,
+										 char newrelpersistence);
+
+#endif							/* SEQLOCALAM_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 96c98e800c22..6b3b3e1e44c1 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 0d289d77fcf7..6ffbcb2c4735 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.c
  *
  * Portions Copyright (c) 1996-2025, 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.h"
+#include "access/seqlocalam.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..a15ceec1c0a0 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 ec9ab9b7e9db..8bc0e95e68c0 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2025, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'seqlocalam.c',
+  'seqlocalxlog.c',
   'sequence.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
new file mode 100644
index 000000000000..bc735ebb0838
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -0,0 +1,634 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.c
+ *	  Local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, 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/multixact.h"
+#include "access/seqlocalam.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"
+
+
+/*
+ * 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
+
+/* 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, (Item) 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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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\" (%lld)",
+									RelationGetRelationName(rel),
+									(long long) 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 "heap";
+}
+
+/*
+ * 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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_seq_local_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	*last_value = seq->last_value;
+	*is_called = seq->is_called;
+
+	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/sequence/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c
new file mode 100644
index 000000000000..db0ad969dbf2
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalxlog.c
@@ -0,0 +1,82 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalxlog.c
+ *	  WAL replay logic for local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/access/sequence/seqlocalxlog.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/seqlocalam.h"
+#include "access/xlogutils.h"
+#include "storage/block.h"
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+seq_local_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+seq_local_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record);
+	seq_local_magic *sm;
+
+	if (info != XLOG_SEQ_LOCAL_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = (Page) BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	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_local_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec);
+
+	if (PageAddItem(localpage, (Item) item, itemsz,
+					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "seq_local_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 1b7499726eb0..cc92268937b1 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index a902c6ce6f09..b7ed47d92801 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -50,23 +51,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -96,13 +80,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,
@@ -134,14 +114,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
@@ -174,35 +148,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;
@@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	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)
@@ -292,10 +222,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;
@@ -306,7 +232,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))
@@ -315,40 +240,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 */
@@ -357,106 +250,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, (Item) 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
  *
@@ -468,10 +261,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;
@@ -480,7 +270,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	bool		reset_state = false;
 	bool		is_called;
 	int64		last_value;
-	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
 	relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -507,16 +296,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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -526,32 +307,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 */
@@ -580,8 +339,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
@@ -596,10 +353,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);
 }
@@ -662,24 +416,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);
@@ -724,105 +469,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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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\" (%lld)",
-									RelationGetRelationName(seqrel),
-									(long long) 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;
@@ -832,69 +481,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;
@@ -984,9 +570,6 @@ do_setval(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,
@@ -1020,9 +603,6 @@ do_setval(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),
@@ -1044,37 +624,8 @@ do_setval(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);
 }
@@ -1215,62 +766,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
@@ -1855,16 +1350,13 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		Buffer		buf;
-		HeapTupleData seqtuple;
-		Form_pg_sequence_data seq;
+		bool		is_called;
+		int64		last_value;
 
-		seq = read_seq_tuple(seqrel, &buf, &seqtuple);
+		seq_local_get_state(seqrel, &last_value, &is_called);
 
-		values[0] = Int64GetDatum(seq->last_value);
-		values[1] = BoolGetDatum(seq->is_called);
-
-		UnlockReleaseBuffer(buf);
+		values[0] = Int64GetDatum(last_value);
+		values[1] = BoolGetDatum(is_called);
 	}
 	else
 		memset(isnull, true, sizeof(isnull));
@@ -1908,17 +1400,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);
 	}
+
 	sequence_close(seqrel, NoLock);
 
 	if (is_called)
@@ -1927,57 +1411,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
-
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = (Page) BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, (Item) item, itemsz,
-					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1992,14 +1425,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c767e..8d1195de2637 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/seqlocaldesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index fac509ed134e..2fcf9fc4392a 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index f26d75e01cfd..2495a28d26a1 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.49.0

v13-0004-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From 78bc650cb1f63c81db57958d809ce7fbc006e6ae Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 4 Dec 2024 08:09:46 +0900
Subject: [PATCH v13 4/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               |  15 --
 src/include/access/sequenceam.h               | 181 ++++++++++++++++++
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/seqlocalam.c      |  41 +++-
 src/backend/access/sequence/sequence.c        |   3 +-
 src/backend/access/sequence/sequenceamapi.c   | 145 ++++++++++++++
 src/backend/catalog/heap.c                    |   6 +-
 src/backend/commands/amcmds.c                 |  16 ++
 src/backend/commands/sequence.c               |  23 +--
 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_tables.c           |  12 ++
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/bin/psql/describe.c                       |   2 +
 src/bin/psql/tab-complete.in.c                |   4 +-
 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 +-
 40 files changed, 695 insertions(+), 182 deletions(-)
 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
index 225fb9a2cbeb..21936511ac2b 100644
--- a/src/include/access/seqlocalam.h
+++ b/src/include/access/seqlocalam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_SEQ_LOCAL_LOG			0x00
@@ -41,18 +40,4 @@ 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);
 
-/* 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);
-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..ac48c8b468be
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,181 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2024, 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.
+	 */
+	void		(*get_state) (Relation rel, int64 *last_value, bool *is_called);
+
+	/*
+	 * 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)
+{
+	rel->rd_sequenceam->get_state(rel, last_value, is_called);
+}
+
+/*
+ * 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 26d15928a155..8f076fcec958 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 6e98a0930c27..080bea5031a9 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 07d182da796a..012a2863c037 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 8b68b16d79da..0da1d882467b 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',
@@ -7846,6 +7852,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 6dca77e0a22f..c42531b25533 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -627,6 +627,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 dd22b5efdfd9..6790728aced3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -145,6 +145,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 9ac0b67683d3..7693e9941fc9 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index d1ca24dd32f0..b1c4155c9a91 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 d637a2381c12..6f48d9eec2e8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3214,6 +3214,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 0f1e74f96c9b..7aba166746ac 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 d94fddd7cef7..6a008d348568 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 a15ceec1c0a0..62006165a15f 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = seqlocalam.o seqlocalxlog.o sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 8bc0e95e68c0..d82af34d538c 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -4,4 +4,5 @@ backend_sources += files(
   'seqlocalam.c',
   'seqlocalxlog.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
index bc735ebb0838..946c7f87a686 100644
--- a/src/backend/access/sequence/seqlocalam.c
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -17,6 +17,7 @@
 
 #include "access/multixact.h"
 #include "access/seqlocalam.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -24,6 +25,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -230,10 +232,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;
@@ -417,7 +419,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 "heap";
@@ -432,7 +434,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];
@@ -499,7 +501,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;
@@ -547,7 +549,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;
@@ -600,7 +602,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)
 {
 	Buffer		buf;
@@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called)
  *
  * Persistence change for the local sequence Relation.
  */
-void
+static void
 seq_local_change_persistence(Relation rel, char newrelpersistence)
 {
 	Buffer		buf;
@@ -632,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 8b5303553702..f30317d1feae 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..dd1a60d827a4
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2024, 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/xact.h"
+#include "access/sequenceam.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 bd3554c0bfdc..14780b4f825c 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1480,9 +1480,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 58ed9d216cc0..e44633d13b60 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 b7ed47d92801..e88ace9b4a4d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -152,6 +152,7 @@ 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;
@@ -173,7 +174,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)
@@ -241,7 +242,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 */
@@ -297,7 +298,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
 
 	/* Read sequence data */
-	seq_local_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -310,7 +311,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 */
@@ -353,7 +354,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);
 }
@@ -470,8 +471,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;
@@ -625,7 +626,7 @@ do_setval(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);
 }
@@ -1353,7 +1354,7 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		bool		is_called;
 		int64		last_value;
 
-		seq_local_get_state(seqrel, &last_value, &is_called);
+		sequence_get_state(seqrel, &last_value, &is_called);
 
 		values[0] = Int64GetDatum(last_value);
 		values[1] = BoolGetDatum(is_called);
@@ -1400,7 +1401,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		seq_local_get_state(seqrel, &result, &is_called);
+		sequence_get_state(seqrel, &result, &is_called);
 	}
 
 	sequence_close(seqrel, NoLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 04ee8ed38be1..eb74bd1beafd 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"
@@ -1005,14 +1006,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)
 	{
@@ -1025,6 +1030,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 f6229089cd15..ee049b763ae2 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 0fc502a3a406..95c4fdfe956b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -381,6 +381,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
@@ -4937,23 +4938,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;
@@ -4990,6 +4994,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); }
 		;
@@ -5993,6 +6002,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 896a7f2c59b4..8eafbe88cb34 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"
@@ -518,6 +519,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 317a1f2b282f..68f160dda7d3 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 9f54a9e72b73..708d0f7a46b5 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);
@@ -1199,8 +1202,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)
 	{
@@ -1209,6 +1211,8 @@ retry:
 		 * inherit.
 		 */
 	}
+	else if (RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind))
+		RelationInitSequenceAccessMethod(relation);
 	else
 		Assert(relation->rd_rel->relam == InvalidOid);
 
@@ -1805,17 +1809,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.
@@ -1846,6 +1842,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
  *
@@ -3663,14 +3702,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
@@ -4295,13 +4337,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);
@@ -6353,8 +6403,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);
@@ -6366,6 +6418,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_tables.c b/src/backend/utils/misc/guc_tables.c
index 76c7c6bb4b17..1380ae59d792 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -30,6 +30,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"
@@ -4329,6 +4330,17 @@ struct config_string ConfigureNamesString[] =
 		check_default_table_access_method, NULL, NULL
 	},
 
+	{
+		{"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets the default sequence access method for new sequences."),
+			NULL,
+			GUC_IS_NAME
+		},
+		&default_sequence_access_method,
+		DEFAULT_SEQUENCE_ACCESS_METHOD,
+		check_default_sequence_access_method, NULL, NULL
+	},
+
 	{
 		{"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default tablespace to create tables and indexes in."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 7c12434efa2f..11df2d8a5bdd 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -748,6 +748,7 @@ autovacuum_worker_slots = 16	# autovacuum worker slots to allocate
 					#   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 bf565afcc4ef..cce7c76fbbdf 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 98951aef82ca..f122e64e6305 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2562,7 +2562,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");
@@ -3605,7 +3605,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 20bf9ea9cdf7..20b3d8520315 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1943,6 +1943,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 b1d12585eaed..2f061931eb38 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5127,31 +5127,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
@@ -5176,32 +5178,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 8eff3d10d279..622f360d5b3c 100644
--- a/src/test/regress/expected/type_sanity.out
+++ b/src/test/regress/expected/type_sanity.out
@@ -507,21 +507,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 2fb3a8528781..e60eeab33c02 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 303f90955d15..aade05c0e172 100644
--- a/src/test/regress/sql/type_sanity.sql
+++ b/src/test/regress/sql/type_sanity.sql
@@ -364,18 +364,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 1279b69422a5..0cc7872dd81c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2654,6 +2654,7 @@ SeqScanState
 SeqTable
 SeqTableData
 SeqType
+SequenceAmRoutine
 SequenceItem
 SerCommitSeqNo
 SerialControl
@@ -3696,6 +3697,7 @@ list_sort_comparator
 loc_chunk
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 local_ts_iter
 local_ts_radix_tree
@@ -3984,7 +3986,6 @@ scram_state_enum
 script_error_callback_arg
 security_class_t
 sem_t
-sepgsql_context_info_t
 sequence_magic
 set_join_pathlist_hook_type
 set_rel_pathlist_hook_type
@@ -4209,6 +4210,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
@@ -4220,7 +4222,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.49.0

v13-0005-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From 337873da8a9546e2a3e0bd4e40177f587c11fd2d Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Aug 2024 13:25:19 +0900
Subject: [PATCH v13 5/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     | 49 +++++++++++++++++++++
 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, 207 insertions(+), 11 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 658986de6f83..f25d473c13cf 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
@@ -190,6 +191,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 82d51c89ac67..4c0a531d21d6 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -181,6 +181,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;
@@ -1241,6 +1242,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;
@@ -2396,6 +2398,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 */
 
@@ -2625,6 +2628,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);
@@ -2729,6 +2733,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);
 
@@ -3402,6 +3409,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;
 
@@ -3564,6 +3574,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.
  */
@@ -3773,6 +3834,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);
 
@@ -4291,6 +4353,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);
@@ -5030,6 +5094,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -5089,6 +5154,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 a2064f471ede..5a091c03ce11 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 */
 
@@ -354,6 +356,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;
@@ -395,6 +398,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 e41e645f649c..7877c35e834c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -134,6 +134,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
@@ -490,6 +491,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},
@@ -1177,6 +1179,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;
@@ -1298,6 +1301,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 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"));
@@ -13742,6 +13746,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;
@@ -18131,26 +18138,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);
@@ -18170,6 +18191,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);
@@ -18241,6 +18266,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);
 	}
@@ -18363,6 +18389,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 2ea574b0f065..31b9470a86fd 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -97,6 +97,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;
@@ -169,6 +170,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},
@@ -451,6 +453,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)
@@ -705,6 +709,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 47f7b0dd3a13..55dde57f1edc 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -69,6 +69,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;
@@ -124,6 +125,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},
@@ -415,6 +417,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;
@@ -546,6 +549,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(_("  --section=SECTION            restore named section (pre-data, data, or post-data)\n"));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 51ebf8ad13c1..d07056dd53d5 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -604,6 +604,13 @@ 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', 'postgres',
+		],
+	},
 	no_table_access_method => {
 		dump_cmd => [
 			'pg_dump', '--no-sync',
@@ -822,6 +829,7 @@ my %full_runs = (
 	no_policies => 1,
 	no_privs => 1,
 	no_statistics => 1,
+	no_sequence_access_method => 1,
 	no_table_access_method => 1,
 	pg_dumpall_dbprivs => 1,
 	pg_dumpall_exclude => 1,
@@ -4701,6 +4709,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
@@ -4729,6 +4749,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 bfc1e7b35247..894b77146697 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1173,6 +1173,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 765b30a3a668..379f985ca5e8 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 c840a807ae90..66a54347295d 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -783,6 +783,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.49.0

v13-0006-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From 21f58845f27ad36595207e0522b0a8a22fffafcb Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v13 6/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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 65ab95be3707..c07ada554312 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9702,6 +9702,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 fef9584f908e..a80de08dd26e 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -95,6 +95,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 af476c82fcc1..9f369cc89930 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..3067dc4d4df0 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>SEQUENCE</literal> and
+      <literal>INDEX</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 1e283f13d15c..52c6096e4ba2 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..a96170bfac03
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.49.0

v13-0007-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From 3d8c5f3c9e2c6cd4eeac4a4f5d9870feafc89857 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 26 Apr 2024 15:09:00 +0900
Subject: [PATCH v13 7/7] snowflake: Add sequence AM based on it

This includes documentation and a basic implementation, though this is
in a very early stage.
---
 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            | 570 +++++++++++++++++++++++
 contrib/snowflake/snowflake.control      |   5 +
 contrib/snowflake/sql/snowflake.sql      |  29 ++
 13 files changed, 858 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 a80de08dd26e..bee964856ece 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -159,6 +159,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 ed30ee7d639f..e3bd2b464172 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..567669eea790
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2024, 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 += bloom
+
+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..be4c4039ecd0
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,570 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2024, 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/sequenceam.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 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(snowflake_magic));
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+	sm->magic = SNOWFLAKE_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 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, (Item) 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)
+{
+	Page		page;
+	ItemId		lp;
+	snowflake_magic *sm;
+	Form_snowflake_data	seq;
+
+	*buf = ReadBuffer(rel, 0);
+	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(*buf);
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+
+	if (sm->magic != SNOWFLAKE_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_snowflake_data) GETSTRUCT(seqdatatuple);
+
+	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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_snowflake_data seq;
+
+	seq = read_snowflake(rel, &buf, &seqdatatuple);
+	*last_value = seq->count;
+	*is_called = seq->is_called;
+	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.49.0

#32Kirill Reshke
reshkekirill@gmail.com
In reply to: Michael Paquier (#31)
Re: Sequence Access Methods, round two

On Fri, 28 Mar 2025 at 09:31, Michael Paquier <michael@paquier.xyz> wrote:

On Tue, Feb 25, 2025 at 04:25:37PM +0900, Michael Paquier wrote:

Rebased.

Conflict in pg_class.h. Rebased.
--
Michael

Looks like we entered a rebase loop here. I'm really interested in
moving this forward; furthermore, I am inclined to include these
patches into our PostgreSQL fork with modifications that we use for
sharding and cloud.

So here is my 2c:

At first, general thought: Access Method is a term we use for
relations. Table access method is something that stores data and
knowledge on how to read this data, and index access method is
something that hints what data we should retrieve using some predicate
(or quals). But sequence... is generating data primitive? So, maybe
not Sequence access method but Sequence generate method? OTOH this
patch provides a way for generating sequence values by _accessing_
remote storage or procedures, so maybe we are fine with this wording.

patches:

0003:
Patch uses "local" wording for build-in sequence, which is not
precise. As I understand, snowflake sequence state is local too.

0004:

+ /*
+ * Retrieve table access method used by a sequence to store its metadata.
+ */
+ const char *(*get_table_am) (void);

Looks like we force the sequence to have some table storage. I can
imagine cases where local storage is not used (and thus, not logged),
so can we have this optional?

0007:
So, we use generic xlog for logging very small actual changes. I
understand this is out of topic here, but can we add a second option
for generic log to store FormData_snowflake_data which is a few bytes?
Maybe we should start different thread for that

--
Best regards,
Kirill Reshke

#33Michael Paquier
michael@paquier.xyz
In reply to: Kirill Reshke (#32)
7 attachment(s)
Re: Sequence Access Methods, round two

On Fri, Mar 28, 2025 at 12:40:17PM +0500, Kirill Reshke wrote:

Looks like we entered a rebase loop here. I'm really interested in
moving this forward; furthermore, I am inclined to include these
patches into our PostgreSQL fork with modifications that we use for
sharding and cloud.

Yes. I do have some folks poking at me about this patch set on a
regular basis, mostly people working on cluster-like applications
where they want to control the source of the sequence computation, and
because they need to maintain some custom forking chirurgy around the
sequence area. So I'd like to get something done for v19 in this
area, but well, few people need to be interested in the whole topic
for the moment. There was an unconference session last year about
that, but there was no consensus reached with the topic drifting
around the copy of sequence data in logical replication setups. What
I have here is different. It cannot come down to me to take a
decision and do something; that's not the way things work around here.

At first, general thought: Access Method is a term we use for
relations. Table access method is something that stores data and
knowledge on how to read this data, and index access method is
something that hints what data we should retrieve using some predicate
(or quals). But sequence... is generating data primitive? So, maybe
not Sequence access method but Sequence generate method? OTOH this
patch provides a way for generating sequence values by _accessing_
remote storage or procedures, so maybe we are fine with this wording.

"Access method" is kind of fit for me here. We have all the
infrastructure in place for this purpose, with the utility commands
and such. Duplicating all that seems a waste, and sequences are
relations.

patches:

0003:
Patch uses "local" wording for build-in sequence, which is not
precise. As I understand, snowflake sequence state is local too.

Yes, I am not wedded to this name.

+ /*
+ * Retrieve table access method used by a sequence to store its metadata.
+ */
+ const char *(*get_table_am) (void);

Looks like we force the sequence to have some table storage. I can
imagine cases where local storage is not used (and thus, not logged),
so can we have this optional?

The patch set gives the option for sequences to define the set of
attributes they want, and it's up to the callbacks to decide if they
want to interact with the storage. An implementation can also choose
to not WAL-log anything if they want; the callbacks have been designed
to allow this case (see the in-memory sequence computation from
upthread). The point is that not supporting a NULL makes all the
logic around the relcache much easier to think about, for little gain,
actually. And we still need to support the default case where a heap
relation is required for the current in-core "local" sequences. So
I'm not really excited about such cases as a whole.

0007:
So, we use generic xlog for logging very small actual changes. I
understand this is out of topic here, but can we add a second option
for generic log to store FormData_snowflake_data which is a few bytes?
Maybe we should start different thread for that

Without an agreement about how the basic callbacks should be shaped, I
don't think that this is worth the time at this stage.

Spoiler: I'm pretty happy with the way things are done in the patch
as with 64-bit fields for sequences mapping only to integers (no 128b
or more, no custom types). One is already set for life with 8 bytes.

I am rebasing the patch set, there were a few things with the removal
of the %lld and their associated (long long) casts.
--
Michael

Attachments:

v14-0001-Remove-FormData_pg_sequence_data-from-init_param.patchtext/x-diff; charset=us-asciiDownload
From d6e69fd5dd731a9b1af7f911c9c83635903a2390 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:00:45 +0900
Subject: [PATCH v14 1/7] Remove FormData_pg_sequence_data from
 init_params()/sequence.c

init_params() sets up "last_value" and "is_called" for a sequence, based
on the sequence properties in pg_sequences.  This simplifies the logic
around log_cnt, which is reset to 0 when the metadata of a sequence is
expected to start from afresh when its properties are updated.
---
 src/backend/commands/sequence.c | 81 ++++++++++++++++++++-------------
 1 file changed, 49 insertions(+), 32 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 451ae6f7f694..08744c3e9112 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -106,7 +106,9 @@ static Form_pg_sequence_data read_seq_tuple(Relation rel,
 static void init_params(ParseState *pstate, List *options, bool for_identity,
 						bool isInit,
 						Form_pg_sequence seqform,
-						Form_pg_sequence_data seqdataform,
+						int64 *last_value,
+						bool *reset_state,
+						bool *is_called,
 						bool *need_seq_rewrite,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
@@ -121,7 +123,9 @@ ObjectAddress
 DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 {
 	FormData_pg_sequence seqform;
-	FormData_pg_sequence_data seqdataform;
+	int64		last_value;
+	bool		reset_state;
+	bool		is_called;
 	bool		need_seq_rewrite;
 	List	   *owned_by;
 	CreateStmt *stmt = makeNode(CreateStmt);
@@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* Check and set all option values */
 	init_params(pstate, seq->options, seq->for_identity, true,
-				&seqform, &seqdataform,
+				&seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/*
@@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 		{
 			case SEQ_COL_LASTVAL:
 				coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
-				value[i - 1] = Int64GetDatumFast(seqdataform.last_value);
+				value[i - 1] = Int64GetDatumFast(last_value);
 				break;
 			case SEQ_COL_LOG:
 				coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
@@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	ObjectAddress address;
 	Relation	rel;
 	HeapTuple	seqtuple;
+	bool		reset_state = false;
+	bool		is_called;
+	int64		last_value;
 	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
@@ -481,12 +488,14 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	/* 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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
-				seqform, newdataform,
+				seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/* If needed, rewrite the sequence relation itself */
@@ -513,6 +522,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * 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);
 	}
 
@@ -1236,17 +1249,19 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 /*
  * init_params: process the options list of CREATE or ALTER SEQUENCE, and
  * store the values into appropriate fields of seqform, for changes that go
- * into the pg_sequence catalog, and fields of seqdataform for changes to the
- * sequence relation itself.  Set *need_seq_rewrite to true if we changed any
- * parameters that require rewriting the sequence's relation (interesting for
- * ALTER SEQUENCE).  Also set *owned_by to any OWNED BY option, or to NIL if
- * there is none.
+ * into the pg_sequence catalog, and fields for changes to the sequence
+ * relation itself (is_called, last_value or any state it may hold).  Set
+ * *need_seq_rewrite to true if we changed any parameters that require
+ * rewriting the sequence's relation (interesting for ALTER SEQUENCE).  Also
+ * set *owned_by to any OWNED BY option, or to NIL if there is none.  Set
+ * *reset_state if the internal state of the sequence needs to change on a
+ * follow-up nextval().
  *
  * If isInit is true, fill any unspecified options with default values;
  * otherwise, do not change existing options that aren't explicitly overridden.
  *
  * Note: we force a sequence rewrite whenever we change parameters that affect
- * generation of future sequence values, even if the seqdataform per se is not
+ * generation of future sequence values, even if the metadata per se is not
  * changed.  This allows ALTER SEQUENCE to behave transactionally.  Currently,
  * the only option that doesn't cause that is OWNED BY.  It's *necessary* for
  * ALTER SEQUENCE OWNED BY to not rewrite the sequence, because that would
@@ -1257,7 +1272,9 @@ static void
 init_params(ParseState *pstate, List *options, bool for_identity,
 			bool isInit,
 			Form_pg_sequence seqform,
-			Form_pg_sequence_data seqdataform,
+			int64 *last_value,
+			bool *reset_state,
+			bool *is_called,
 			bool *need_seq_rewrite,
 			List **owned_by)
 {
@@ -1363,11 +1380,11 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	}
 
 	/*
-	 * We must reset log_cnt when isInit or when changing any parameters that
-	 * would affect future nextval allocations.
+	 * We must reset the state when isInit or when changing any parameters
+	 * that would affect future nextval allocations.
 	 */
 	if (isInit)
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 
 	/* AS type */
 	if (as_type != NULL)
@@ -1416,7 +1433,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("INCREMENT must not be zero")));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1428,7 +1445,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	{
 		seqform->seqcycle = boolVal(is_cycled->arg);
 		Assert(BoolIsValid(seqform->seqcycle));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1439,7 +1456,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (max_value != NULL && max_value->arg)
 	{
 		seqform->seqmax = defGetInt64(max_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || max_value != NULL || reset_max_value)
 	{
@@ -1455,7 +1472,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmax = -1;	/* descending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate maximum value.  No need to check INT8 as seqmax is an int64 */
@@ -1471,7 +1488,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (min_value != NULL && min_value->arg)
 	{
 		seqform->seqmin = defGetInt64(min_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || min_value != NULL || reset_min_value)
 	{
@@ -1487,7 +1504,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmin = 1;	/* ascending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate minimum value.  No need to check INT8 as seqmin is an int64 */
@@ -1538,30 +1555,30 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (restart_value != NULL)
 	{
 		if (restart_value->arg != NULL)
-			seqdataform->last_value = defGetInt64(restart_value);
+			*last_value = defGetInt64(restart_value);
 		else
-			seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
-		seqdataform->log_cnt = 0;
+			*last_value = seqform->seqstart;
+		*is_called = false;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-		seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
+		*last_value = seqform->seqstart;
+		*is_called = false;
 	}
 
 	/* crosscheck RESTART (or current value, if changing MIN/MAX) */
-	if (seqdataform->last_value < seqform->seqmin)
+	if (*last_value < seqform->seqmin)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%" PRId64 ") cannot be less than MINVALUE (%" PRId64 ")",
-						seqdataform->last_value,
+						*last_value,
 						seqform->seqmin)));
-	if (seqdataform->last_value > seqform->seqmax)
+	if (*last_value > seqform->seqmax)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%" PRId64 ") cannot be greater than MAXVALUE (%" PRId64 ")",
-						seqdataform->last_value,
+						*last_value,
 						seqform->seqmax)));
 
 	/* CACHE */
@@ -1573,7 +1590,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("CACHE (%" PRId64 ") must be greater than zero",
 							seqform->seqcache)));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-- 
2.49.0

v14-0002-Integrate-addition-of-attributes-for-sequences-w.patchtext/x-diff; charset=us-asciiDownload
From 22f134c6339956b38b86ac5198949352f66b786c Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v14 2/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               | 29 +++++++++++++++++--
 src/backend/commands/tablecmds.c              | 10 +++++++
 src/backend/tcop/utility.c                    |  4 +++
 .../test_ddl_deparse/expected/alter_table.out | 10 +++++--
 .../expected/create_sequence_1.out            |  5 +++-
 .../expected/create_table.out                 | 15 ++++++++--
 .../test_ddl_deparse/test_ddl_deparse.c       |  3 ++
 8 files changed, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4610fc61293b..5bdea762fc72 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2407,6 +2407,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 08744c3e9112..20e8978c9346 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -136,6 +136,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;
@@ -174,7 +177,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;
@@ -198,7 +200,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;
@@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 2705cf11330d..dc4d459653a8 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4646,6 +4646,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 */
@@ -4941,6 +4942,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,
@@ -5373,6 +5381,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,
@@ -6588,6 +6597,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 25fe3d580166..0629dc5387c0 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 50d0354a3417..ed31059ef584 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
@@ -70,7 +73,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_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
index 5837ea484e40..310ce5a6baf5 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.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 193669f2bc1e..254fdf90c79a 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.49.0

v14-0003-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From a88a044c02f0f17d643f1c90f245ec5acfa31eb5 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 30 Apr 2025 08:26:43 +0900
Subject: [PATCH v14 3/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.
- seqlocalxlog.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/seqlocalam.h               |  58 ++
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 .../rmgrdesc/{seqdesc.c => seqlocaldesc.c}    |  20 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/meson.build       |   2 +
 src/backend/access/sequence/seqlocalam.c      | 634 ++++++++++++++++++
 src/backend/access/sequence/seqlocalxlog.c    |  82 +++
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 634 +-----------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 14 files changed, 822 insertions(+), 622 deletions(-)
 create mode 100644 src/include/access/seqlocalam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%)
 create mode 100644 src/backend/access/sequence/seqlocalam.c
 create mode 100644 src/backend/access/sequence/seqlocalxlog.c

diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 8e7fc9db8778..b942b25350bc 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/seqlocalam.h b/src/include/access/seqlocalam.h
new file mode 100644
index 000000000000..225fb9a2cbeb
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2024, 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 "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_SEQ_LOCAL_LOG			0x00
+
+typedef struct xl_seq_local_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_seq_local_rec;
+
+/*
+ * 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;
+
+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);
+
+/* 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);
+extern void seq_local_change_persistence(Relation rel,
+										 char newrelpersistence);
+
+#endif							/* SEQLOCALAM_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 96c98e800c22..6b3b3e1e44c1 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 0d289d77fcf7..6ffbcb2c4735 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.c
  *
  * Portions Copyright (c) 1996-2025, 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.h"
+#include "access/seqlocalam.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..a15ceec1c0a0 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 ec9ab9b7e9db..8bc0e95e68c0 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2025, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'seqlocalam.c',
+  'seqlocalxlog.c',
   'sequence.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
new file mode 100644
index 000000000000..e019a6f5a95d
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -0,0 +1,634 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.c
+ *	  Local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, 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/multixact.h"
+#include "access/seqlocalam.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"
+
+
+/*
+ * 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
+
+/* 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, (Item) 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 "heap";
+}
+
+/*
+ * 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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_seq_local_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	*last_value = seq->last_value;
+	*is_called = seq->is_called;
+
+	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/sequence/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c
new file mode 100644
index 000000000000..db0ad969dbf2
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalxlog.c
@@ -0,0 +1,82 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalxlog.c
+ *	  WAL replay logic for local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/access/sequence/seqlocalxlog.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/seqlocalam.h"
+#include "access/xlogutils.h"
+#include "storage/block.h"
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+seq_local_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+seq_local_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record);
+	seq_local_magic *sm;
+
+	if (info != XLOG_SEQ_LOCAL_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = (Page) BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	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_local_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec);
+
+	if (PageAddItem(localpage, (Item) item, itemsz,
+					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "seq_local_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 1b7499726eb0..cc92268937b1 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 20e8978c9346..15ea0e24970c 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -50,23 +51,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -96,13 +80,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,
@@ -134,14 +114,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
@@ -174,35 +148,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;
@@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	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)
@@ -292,10 +222,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;
@@ -306,7 +232,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))
@@ -315,40 +240,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 */
@@ -357,106 +250,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, (Item) 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
  *
@@ -468,10 +261,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;
@@ -480,7 +270,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	bool		reset_state = false;
 	bool		is_called;
 	int64		last_value;
-	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
 	relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -507,16 +296,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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -526,32 +307,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 */
@@ -580,8 +339,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
@@ -596,10 +353,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);
 }
@@ -662,24 +416,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);
@@ -724,105 +469,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;
@@ -832,69 +481,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;
@@ -984,9 +570,6 @@ do_setval(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,
@@ -1020,9 +603,6 @@ do_setval(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),
@@ -1044,37 +624,8 @@ do_setval(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);
 }
@@ -1215,62 +766,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
@@ -1855,16 +1350,13 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		Buffer		buf;
-		HeapTupleData seqtuple;
-		Form_pg_sequence_data seq;
+		bool		is_called;
+		int64		last_value;
 
-		seq = read_seq_tuple(seqrel, &buf, &seqtuple);
+		seq_local_get_state(seqrel, &last_value, &is_called);
 
-		values[0] = Int64GetDatum(seq->last_value);
-		values[1] = BoolGetDatum(seq->is_called);
-
-		UnlockReleaseBuffer(buf);
+		values[0] = Int64GetDatum(last_value);
+		values[1] = BoolGetDatum(is_called);
 	}
 	else
 		memset(isnull, true, sizeof(isnull));
@@ -1908,17 +1400,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);
 	}
+
 	sequence_close(seqrel, NoLock);
 
 	if (is_called)
@@ -1927,57 +1411,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
-
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = (Page) BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, (Item) item, itemsz,
-					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1992,14 +1425,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c767e..8d1195de2637 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/seqlocaldesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index fac509ed134e..2fcf9fc4392a 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index f26d75e01cfd..2495a28d26a1 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.49.0

v14-0004-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From ccdef9ec55c72cb003c3d66f537707dfa256f0e9 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 4 Dec 2024 08:09:46 +0900
Subject: [PATCH v14 4/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               |  15 --
 src/include/access/sequenceam.h               | 181 ++++++++++++++++++
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/seqlocalam.c      |  41 +++-
 src/backend/access/sequence/sequence.c        |   3 +-
 src/backend/access/sequence/sequenceamapi.c   | 145 ++++++++++++++
 src/backend/catalog/heap.c                    |   6 +-
 src/backend/commands/amcmds.c                 |  16 ++
 src/backend/commands/sequence.c               |  23 +--
 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_tables.c           |  12 ++
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/bin/psql/describe.c                       |   2 +
 src/bin/psql/tab-complete.in.c                |   4 +-
 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 +-
 40 files changed, 695 insertions(+), 182 deletions(-)
 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
index 225fb9a2cbeb..21936511ac2b 100644
--- a/src/include/access/seqlocalam.h
+++ b/src/include/access/seqlocalam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_SEQ_LOCAL_LOG			0x00
@@ -41,18 +40,4 @@ 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);
 
-/* 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);
-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..ac48c8b468be
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,181 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2024, 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.
+	 */
+	void		(*get_state) (Relation rel, int64 *last_value, bool *is_called);
+
+	/*
+	 * 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)
+{
+	rel->rd_sequenceam->get_state(rel, last_value, is_called);
+}
+
+/*
+ * 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 26d15928a155..8f076fcec958 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 6e98a0930c27..080bea5031a9 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 07d182da796a..012a2863c037 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 62beb71da288..191489fcfcca 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',
@@ -7882,6 +7888,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 6dca77e0a22f..c42531b25533 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -627,6 +627,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 dd22b5efdfd9..6790728aced3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -145,6 +145,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 9ac0b67683d3..7693e9941fc9 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index d1ca24dd32f0..b1c4155c9a91 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 5bdea762fc72..79465fe4c54d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3216,6 +3216,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 799fa7ace684..82bb81561671 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 b552359915f1..4e418968253d 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 a15ceec1c0a0..62006165a15f 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = seqlocalam.o seqlocalxlog.o sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 8bc0e95e68c0..d82af34d538c 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -4,4 +4,5 @@ backend_sources += files(
   'seqlocalam.c',
   'seqlocalxlog.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
index e019a6f5a95d..5dec9d51ec82 100644
--- a/src/backend/access/sequence/seqlocalam.c
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -17,6 +17,7 @@
 
 #include "access/multixact.h"
 #include "access/seqlocalam.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -24,6 +25,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -230,10 +232,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;
@@ -417,7 +419,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 "heap";
@@ -432,7 +434,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];
@@ -499,7 +501,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;
@@ -547,7 +549,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;
@@ -600,7 +602,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)
 {
 	Buffer		buf;
@@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called)
  *
  * Persistence change for the local sequence Relation.
  */
-void
+static void
 seq_local_change_persistence(Relation rel, char newrelpersistence)
 {
 	Buffer		buf;
@@ -632,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 8b5303553702..f30317d1feae 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..dd1a60d827a4
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2024, 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/xact.h"
+#include "access/sequenceam.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 fbaed5359ad7..558e9a8f9695 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1480,9 +1480,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 58ed9d216cc0..e44633d13b60 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 15ea0e24970c..fc2c18df4dc3 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -152,6 +152,7 @@ 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;
@@ -173,7 +174,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)
@@ -241,7 +242,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 */
@@ -297,7 +298,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
 
 	/* Read sequence data */
-	seq_local_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -310,7 +311,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 */
@@ -353,7 +354,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);
 }
@@ -470,8 +471,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;
@@ -625,7 +626,7 @@ do_setval(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);
 }
@@ -1353,7 +1354,7 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		bool		is_called;
 		int64		last_value;
 
-		seq_local_get_state(seqrel, &last_value, &is_called);
+		sequence_get_state(seqrel, &last_value, &is_called);
 
 		values[0] = Int64GetDatum(last_value);
 		values[1] = BoolGetDatum(is_called);
@@ -1400,7 +1401,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		seq_local_get_state(seqrel, &result, &is_called);
+		sequence_get_state(seqrel, &result, &is_called);
 	}
 
 	sequence_close(seqrel, NoLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dc4d459653a8..5fa8928119f9 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"
@@ -1025,14 +1026,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)
 	{
@@ -1045,6 +1050,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 77659b0f7602..5e4ff23cf1e4 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 3c4268b271a4..3a287c7f87a9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -381,6 +381,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
@@ -4941,23 +4942,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;
@@ -4994,6 +4998,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); }
 		;
@@ -5997,6 +6006,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 62015431fdf1..9ba0febe63c4 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"
@@ -518,6 +519,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 317a1f2b282f..68f160dda7d3 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 68ff67de549a..2f6c0a12f3e3 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);
 
@@ -1831,17 +1835,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.
@@ -1872,6 +1868,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
  *
@@ -3711,14 +3750,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
@@ -4343,13 +4385,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);
@@ -6422,8 +6472,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);
@@ -6435,6 +6487,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_tables.c b/src/backend/utils/misc/guc_tables.c
index 2f8cbd867599..daa550113a1f 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"
@@ -4341,6 +4342,17 @@ struct config_string ConfigureNamesString[] =
 		check_default_table_access_method, NULL, NULL
 	},
 
+	{
+		{"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets the default sequence access method for new sequences."),
+			NULL,
+			GUC_IS_NAME
+		},
+		&default_sequence_access_method,
+		DEFAULT_SEQUENCE_ACCESS_METHOD,
+		check_default_sequence_access_method, NULL, NULL
+	},
+
 	{
 		{"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default tablespace to create tables and indexes in."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 34826d01380b..911528bd0da6 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -754,6 +754,7 @@ autovacuum_worker_slots = 16	# autovacuum worker slots to allocate
 					#   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 1d08268393e3..4c7a9446b96c 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 c916b9299a80..5f8a5989adc5 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2562,7 +2562,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");
@@ -3605,7 +3605,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 20bf9ea9cdf7..20b3d8520315 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1943,6 +1943,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 cf48ae6d0c2e..2b7d6c69e1e1 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5127,31 +5127,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
@@ -5176,32 +5178,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 dd0c52ab08b5..c3218a36bc48 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 2fb3a8528781..e60eeab33c02 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 c94dd83d3061..f1b6cd1091ef 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 e5879e00dffe..e0654ddecd48 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2679,6 +2679,7 @@ SeqScanState
 SeqTable
 SeqTableData
 SeqType
+SequenceAmRoutine
 SequenceItem
 SerCommitSeqNo
 SerialControl
@@ -3723,6 +3724,7 @@ list_sort_comparator
 loc_chunk
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 local_ts_iter
 local_ts_radix_tree
@@ -4011,7 +4013,6 @@ scram_state_enum
 script_error_callback_arg
 security_class_t
 sem_t
-sepgsql_context_info_t
 sequence_magic
 set_join_pathlist_hook_type
 set_rel_pathlist_hook_type
@@ -4236,6 +4237,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
@@ -4247,7 +4249,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.49.0

v14-0005-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From 317d970805198e89bca7de42d344c807c4eae9f5 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Aug 2024 13:25:19 +0900
Subject: [PATCH v14 5/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     | 49 +++++++++++++++++++++
 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, 207 insertions(+), 11 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index af0007fb6d2f..62d86d69db45 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
@@ -190,6 +191,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 afa42337b110..3d63d8f6f6b5 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -181,6 +181,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;
@@ -1246,6 +1247,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;
@@ -2405,6 +2407,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 */
 
@@ -2672,6 +2675,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);
@@ -2776,6 +2780,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);
 
@@ -3462,6 +3469,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;
 
@@ -3624,6 +3634,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.
  */
@@ -3833,6 +3894,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);
 
@@ -4389,6 +4451,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);
@@ -5128,6 +5192,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -5187,6 +5252,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 365073b3eae4..89b5cec22ac1 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;
@@ -404,6 +407,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 e2e7975b34e0..543a1d958436 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -134,6 +134,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
@@ -495,6 +496,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},
@@ -1182,6 +1184,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;
@@ -1303,6 +1306,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 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"));
@@ -14084,6 +14088,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;
@@ -18492,26 +18499,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);
@@ -18531,6 +18552,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);
@@ -18602,6 +18627,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);
 	}
@@ -18724,6 +18750,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 946a6d0fafc6..f8c6a74320ad 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -93,6 +93,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;
@@ -164,6 +165,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},
@@ -465,6 +467,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)
@@ -738,6 +742,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 f2182e918256..73826fed9803 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -98,6 +98,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;
@@ -154,6 +155,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},
@@ -459,6 +461,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;
@@ -702,6 +705,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(_("  --section=SECTION            restore named section (pre-data, data, or post-data)\n"));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 55d892d9c162..6c913d568b44 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -604,6 +604,13 @@ 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', 'postgres',
+		],
+	},
 	no_table_access_method => {
 		dump_cmd => [
 			'pg_dump', '--no-sync',
@@ -822,6 +829,7 @@ my %full_runs = (
 	no_policies => 1,
 	no_privs => 1,
 	no_statistics => 1,
+	no_sequence_access_method => 1,
 	no_table_access_method => 1,
 	pg_dumpall_dbprivs => 1,
 	pg_dumpall_exclude => 1,
@@ -4773,6 +4781,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
@@ -4801,6 +4821,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 b757d27ebd0b..dda753e1cf25 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1173,6 +1173,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 43fdab2d77ed..17681045996a 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -605,6 +605,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 b6de497aee18..78d5cd7e6011 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -829,6 +829,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.49.0

v14-0006-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From a93b0a521fdf82517ca8f16ecc880e9b01cfd3fb Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v14 6/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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 fd6e3e028907..659a37960579 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9752,6 +9752,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 fef9584f908e..a80de08dd26e 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -95,6 +95,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 af476c82fcc1..9f369cc89930 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..3067dc4d4df0 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>SEQUENCE</literal> and
+      <literal>INDEX</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 1e283f13d15c..52c6096e4ba2 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..a96170bfac03
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.49.0

v14-0007-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From 9fd87858065fc47bf3efd7ec867b2319e12000b6 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 26 Apr 2024 15:09:00 +0900
Subject: [PATCH v14 7/7] snowflake: Add sequence AM based on it

This includes documentation and a basic implementation, though this is
in a very early stage.
---
 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            | 570 +++++++++++++++++++++++
 contrib/snowflake/snowflake.control      |   5 +
 contrib/snowflake/sql/snowflake.sql      |  29 ++
 13 files changed, 858 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 a80de08dd26e..bee964856ece 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -159,6 +159,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 ed30ee7d639f..e3bd2b464172 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..567669eea790
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2024, 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 += bloom
+
+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..be4c4039ecd0
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,570 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2024, 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/sequenceam.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 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(snowflake_magic));
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+	sm->magic = SNOWFLAKE_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 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, (Item) 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)
+{
+	Page		page;
+	ItemId		lp;
+	snowflake_magic *sm;
+	Form_snowflake_data	seq;
+
+	*buf = ReadBuffer(rel, 0);
+	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(*buf);
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+
+	if (sm->magic != SNOWFLAKE_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_snowflake_data) GETSTRUCT(seqdatatuple);
+
+	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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_snowflake_data seq;
+
+	seq = read_snowflake(rel, &buf, &seqdatatuple);
+	*last_value = seq->count;
+	*is_called = seq->is_called;
+	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.49.0

#34Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#33)
7 attachment(s)
Re: Sequence Access Methods, round two

On Wed, Apr 30, 2025 at 09:08:51AM +0900, Michael Paquier wrote:

I am rebasing the patch set, there were a few things with the removal
of the %lld and their associated (long long) casts.

Rebased as v15, following a report from the CI that the new pg_dump
command in the TAP tests needed a --with-statistics switch, like its
relatives.
--
Michael

Attachments:

v15-0001-Remove-FormData_pg_sequence_data-from-init_param.patchtext/x-diff; charset=us-asciiDownload
From 70edea37f5096bed14944310d5b64ebf7fc2fecd Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:00:45 +0900
Subject: [PATCH v15 1/7] Remove FormData_pg_sequence_data from
 init_params()/sequence.c

init_params() sets up "last_value" and "is_called" for a sequence, based
on the sequence properties in pg_sequences.  This simplifies the logic
around log_cnt, which is reset to 0 when the metadata of a sequence is
expected to start from afresh when its properties are updated.
---
 src/backend/commands/sequence.c | 81 ++++++++++++++++++++-------------
 1 file changed, 49 insertions(+), 32 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 451ae6f7f694..08744c3e9112 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -106,7 +106,9 @@ static Form_pg_sequence_data read_seq_tuple(Relation rel,
 static void init_params(ParseState *pstate, List *options, bool for_identity,
 						bool isInit,
 						Form_pg_sequence seqform,
-						Form_pg_sequence_data seqdataform,
+						int64 *last_value,
+						bool *reset_state,
+						bool *is_called,
 						bool *need_seq_rewrite,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
@@ -121,7 +123,9 @@ ObjectAddress
 DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 {
 	FormData_pg_sequence seqform;
-	FormData_pg_sequence_data seqdataform;
+	int64		last_value;
+	bool		reset_state;
+	bool		is_called;
 	bool		need_seq_rewrite;
 	List	   *owned_by;
 	CreateStmt *stmt = makeNode(CreateStmt);
@@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* Check and set all option values */
 	init_params(pstate, seq->options, seq->for_identity, true,
-				&seqform, &seqdataform,
+				&seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/*
@@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 		{
 			case SEQ_COL_LASTVAL:
 				coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
-				value[i - 1] = Int64GetDatumFast(seqdataform.last_value);
+				value[i - 1] = Int64GetDatumFast(last_value);
 				break;
 			case SEQ_COL_LOG:
 				coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
@@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	ObjectAddress address;
 	Relation	rel;
 	HeapTuple	seqtuple;
+	bool		reset_state = false;
+	bool		is_called;
+	int64		last_value;
 	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
@@ -481,12 +488,14 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	/* 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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
-				seqform, newdataform,
+				seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/* If needed, rewrite the sequence relation itself */
@@ -513,6 +522,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * 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);
 	}
 
@@ -1236,17 +1249,19 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 /*
  * init_params: process the options list of CREATE or ALTER SEQUENCE, and
  * store the values into appropriate fields of seqform, for changes that go
- * into the pg_sequence catalog, and fields of seqdataform for changes to the
- * sequence relation itself.  Set *need_seq_rewrite to true if we changed any
- * parameters that require rewriting the sequence's relation (interesting for
- * ALTER SEQUENCE).  Also set *owned_by to any OWNED BY option, or to NIL if
- * there is none.
+ * into the pg_sequence catalog, and fields for changes to the sequence
+ * relation itself (is_called, last_value or any state it may hold).  Set
+ * *need_seq_rewrite to true if we changed any parameters that require
+ * rewriting the sequence's relation (interesting for ALTER SEQUENCE).  Also
+ * set *owned_by to any OWNED BY option, or to NIL if there is none.  Set
+ * *reset_state if the internal state of the sequence needs to change on a
+ * follow-up nextval().
  *
  * If isInit is true, fill any unspecified options with default values;
  * otherwise, do not change existing options that aren't explicitly overridden.
  *
  * Note: we force a sequence rewrite whenever we change parameters that affect
- * generation of future sequence values, even if the seqdataform per se is not
+ * generation of future sequence values, even if the metadata per se is not
  * changed.  This allows ALTER SEQUENCE to behave transactionally.  Currently,
  * the only option that doesn't cause that is OWNED BY.  It's *necessary* for
  * ALTER SEQUENCE OWNED BY to not rewrite the sequence, because that would
@@ -1257,7 +1272,9 @@ static void
 init_params(ParseState *pstate, List *options, bool for_identity,
 			bool isInit,
 			Form_pg_sequence seqform,
-			Form_pg_sequence_data seqdataform,
+			int64 *last_value,
+			bool *reset_state,
+			bool *is_called,
 			bool *need_seq_rewrite,
 			List **owned_by)
 {
@@ -1363,11 +1380,11 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	}
 
 	/*
-	 * We must reset log_cnt when isInit or when changing any parameters that
-	 * would affect future nextval allocations.
+	 * We must reset the state when isInit or when changing any parameters
+	 * that would affect future nextval allocations.
 	 */
 	if (isInit)
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 
 	/* AS type */
 	if (as_type != NULL)
@@ -1416,7 +1433,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("INCREMENT must not be zero")));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1428,7 +1445,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	{
 		seqform->seqcycle = boolVal(is_cycled->arg);
 		Assert(BoolIsValid(seqform->seqcycle));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1439,7 +1456,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (max_value != NULL && max_value->arg)
 	{
 		seqform->seqmax = defGetInt64(max_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || max_value != NULL || reset_max_value)
 	{
@@ -1455,7 +1472,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmax = -1;	/* descending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate maximum value.  No need to check INT8 as seqmax is an int64 */
@@ -1471,7 +1488,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (min_value != NULL && min_value->arg)
 	{
 		seqform->seqmin = defGetInt64(min_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || min_value != NULL || reset_min_value)
 	{
@@ -1487,7 +1504,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmin = 1;	/* ascending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate minimum value.  No need to check INT8 as seqmin is an int64 */
@@ -1538,30 +1555,30 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (restart_value != NULL)
 	{
 		if (restart_value->arg != NULL)
-			seqdataform->last_value = defGetInt64(restart_value);
+			*last_value = defGetInt64(restart_value);
 		else
-			seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
-		seqdataform->log_cnt = 0;
+			*last_value = seqform->seqstart;
+		*is_called = false;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-		seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
+		*last_value = seqform->seqstart;
+		*is_called = false;
 	}
 
 	/* crosscheck RESTART (or current value, if changing MIN/MAX) */
-	if (seqdataform->last_value < seqform->seqmin)
+	if (*last_value < seqform->seqmin)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%" PRId64 ") cannot be less than MINVALUE (%" PRId64 ")",
-						seqdataform->last_value,
+						*last_value,
 						seqform->seqmin)));
-	if (seqdataform->last_value > seqform->seqmax)
+	if (*last_value > seqform->seqmax)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%" PRId64 ") cannot be greater than MAXVALUE (%" PRId64 ")",
-						seqdataform->last_value,
+						*last_value,
 						seqform->seqmax)));
 
 	/* CACHE */
@@ -1573,7 +1590,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("CACHE (%" PRId64 ") must be greater than zero",
 							seqform->seqcache)));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-- 
2.49.0

v15-0002-Integrate-addition-of-attributes-for-sequences-w.patchtext/x-diff; charset=us-asciiDownload
From cba41c2632959bd1c82558a2f226274c4dd45053 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v15 2/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               | 29 +++++++++++++++++--
 src/backend/commands/tablecmds.c              | 10 +++++++
 src/backend/tcop/utility.c                    |  4 +++
 .../test_ddl_deparse/expected/alter_table.out | 10 +++++--
 .../expected/create_sequence_1.out            |  5 +++-
 .../expected/create_table.out                 | 15 ++++++++--
 .../test_ddl_deparse/test_ddl_deparse.c       |  3 ++
 8 files changed, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ba12678d1cbd..748016726c07 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2412,6 +2412,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 08744c3e9112..20e8978c9346 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -136,6 +136,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;
@@ -174,7 +177,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;
@@ -198,7 +200,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;
@@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 ea96947d8130..96c3a9eb086f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4646,6 +4646,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 */
@@ -4941,6 +4942,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,
@@ -5373,6 +5381,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,
@@ -6588,6 +6597,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 25fe3d580166..0629dc5387c0 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 50d0354a3417..ed31059ef584 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
@@ -70,7 +73,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_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
index 5837ea484e40..310ce5a6baf5 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.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 193669f2bc1e..254fdf90c79a 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.49.0

v15-0003-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From b25345d4a709b40cec04f39d28b0c1f510d249ac Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 30 Apr 2025 08:26:43 +0900
Subject: [PATCH v15 3/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.
- seqlocalxlog.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/seqlocalam.h               |  58 ++
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 .../rmgrdesc/{seqdesc.c => seqlocaldesc.c}    |  20 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/meson.build       |   2 +
 src/backend/access/sequence/seqlocalam.c      | 634 ++++++++++++++++++
 src/backend/access/sequence/seqlocalxlog.c    |  82 +++
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 634 +-----------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 14 files changed, 822 insertions(+), 622 deletions(-)
 create mode 100644 src/include/access/seqlocalam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%)
 create mode 100644 src/backend/access/sequence/seqlocalam.c
 create mode 100644 src/backend/access/sequence/seqlocalxlog.c

diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 8e7fc9db8778..b942b25350bc 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/seqlocalam.h b/src/include/access/seqlocalam.h
new file mode 100644
index 000000000000..225fb9a2cbeb
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2024, 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 "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_SEQ_LOCAL_LOG			0x00
+
+typedef struct xl_seq_local_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_seq_local_rec;
+
+/*
+ * 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;
+
+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);
+
+/* 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);
+extern void seq_local_change_persistence(Relation rel,
+										 char newrelpersistence);
+
+#endif							/* SEQLOCALAM_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 96c98e800c22..6b3b3e1e44c1 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 0d289d77fcf7..6ffbcb2c4735 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.c
  *
  * Portions Copyright (c) 1996-2025, 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.h"
+#include "access/seqlocalam.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..a15ceec1c0a0 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 ec9ab9b7e9db..8bc0e95e68c0 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2025, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'seqlocalam.c',
+  'seqlocalxlog.c',
   'sequence.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
new file mode 100644
index 000000000000..e019a6f5a95d
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -0,0 +1,634 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.c
+ *	  Local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, 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/multixact.h"
+#include "access/seqlocalam.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"
+
+
+/*
+ * 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
+
+/* 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, (Item) 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 "heap";
+}
+
+/*
+ * 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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_seq_local_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	*last_value = seq->last_value;
+	*is_called = seq->is_called;
+
+	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/sequence/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c
new file mode 100644
index 000000000000..db0ad969dbf2
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalxlog.c
@@ -0,0 +1,82 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalxlog.c
+ *	  WAL replay logic for local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/access/sequence/seqlocalxlog.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/seqlocalam.h"
+#include "access/xlogutils.h"
+#include "storage/block.h"
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+seq_local_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+seq_local_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record);
+	seq_local_magic *sm;
+
+	if (info != XLOG_SEQ_LOCAL_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = (Page) BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	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_local_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec);
+
+	if (PageAddItem(localpage, (Item) item, itemsz,
+					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "seq_local_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 1b7499726eb0..cc92268937b1 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 20e8978c9346..15ea0e24970c 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -50,23 +51,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -96,13 +80,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,
@@ -134,14 +114,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
@@ -174,35 +148,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;
@@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	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)
@@ -292,10 +222,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;
@@ -306,7 +232,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))
@@ -315,40 +240,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 */
@@ -357,106 +250,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, (Item) 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
  *
@@ -468,10 +261,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;
@@ -480,7 +270,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	bool		reset_state = false;
 	bool		is_called;
 	int64		last_value;
-	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
 	relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -507,16 +296,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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -526,32 +307,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 */
@@ -580,8 +339,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
@@ -596,10 +353,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);
 }
@@ -662,24 +416,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);
@@ -724,105 +469,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;
@@ -832,69 +481,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;
@@ -984,9 +570,6 @@ do_setval(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,
@@ -1020,9 +603,6 @@ do_setval(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),
@@ -1044,37 +624,8 @@ do_setval(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);
 }
@@ -1215,62 +766,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
@@ -1855,16 +1350,13 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		Buffer		buf;
-		HeapTupleData seqtuple;
-		Form_pg_sequence_data seq;
+		bool		is_called;
+		int64		last_value;
 
-		seq = read_seq_tuple(seqrel, &buf, &seqtuple);
+		seq_local_get_state(seqrel, &last_value, &is_called);
 
-		values[0] = Int64GetDatum(seq->last_value);
-		values[1] = BoolGetDatum(seq->is_called);
-
-		UnlockReleaseBuffer(buf);
+		values[0] = Int64GetDatum(last_value);
+		values[1] = BoolGetDatum(is_called);
 	}
 	else
 		memset(isnull, true, sizeof(isnull));
@@ -1908,17 +1400,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);
 	}
+
 	sequence_close(seqrel, NoLock);
 
 	if (is_called)
@@ -1927,57 +1411,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
-
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = (Page) BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, (Item) item, itemsz,
-					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1992,14 +1425,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c767e..8d1195de2637 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/seqlocaldesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index fac509ed134e..2fcf9fc4392a 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index f26d75e01cfd..2495a28d26a1 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.49.0

v15-0004-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From 17072034cd3c96489fd7b5ec74fbe4d4b9f7ab02 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 4 Dec 2024 08:09:46 +0900
Subject: [PATCH v15 4/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               |  15 --
 src/include/access/sequenceam.h               | 181 ++++++++++++++++++
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/seqlocalam.c      |  41 +++-
 src/backend/access/sequence/sequence.c        |   3 +-
 src/backend/access/sequence/sequenceamapi.c   | 145 ++++++++++++++
 src/backend/catalog/heap.c                    |   6 +-
 src/backend/commands/amcmds.c                 |  16 ++
 src/backend/commands/sequence.c               |  23 +--
 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_tables.c           |  12 ++
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/bin/psql/describe.c                       |   2 +
 src/bin/psql/tab-complete.in.c                |   4 +-
 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 +-
 40 files changed, 695 insertions(+), 182 deletions(-)
 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
index 225fb9a2cbeb..21936511ac2b 100644
--- a/src/include/access/seqlocalam.h
+++ b/src/include/access/seqlocalam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_SEQ_LOCAL_LOG			0x00
@@ -41,18 +40,4 @@ 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);
 
-/* 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);
-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..ac48c8b468be
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,181 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2024, 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.
+	 */
+	void		(*get_state) (Relation rel, int64 *last_value, bool *is_called);
+
+	/*
+	 * 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)
+{
+	rel->rd_sequenceam->get_state(rel, last_value, is_called);
+}
+
+/*
+ * 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 26d15928a155..8f076fcec958 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 6e98a0930c27..080bea5031a9 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 07d182da796a..012a2863c037 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 d3d28a263fa9..9f700f6e9c8b 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',
@@ -7882,6 +7888,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 6dca77e0a22f..c42531b25533 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -627,6 +627,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 dd22b5efdfd9..6790728aced3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -145,6 +145,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 9ac0b67683d3..7693e9941fc9 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index d1ca24dd32f0..b1c4155c9a91 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 748016726c07..0f5647c717da 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3221,6 +3221,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 799fa7ace684..82bb81561671 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 b552359915f1..4e418968253d 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 a15ceec1c0a0..62006165a15f 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = seqlocalam.o seqlocalxlog.o sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 8bc0e95e68c0..d82af34d538c 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -4,4 +4,5 @@ backend_sources += files(
   'seqlocalam.c',
   'seqlocalxlog.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
index e019a6f5a95d..5dec9d51ec82 100644
--- a/src/backend/access/sequence/seqlocalam.c
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -17,6 +17,7 @@
 
 #include "access/multixact.h"
 #include "access/seqlocalam.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -24,6 +25,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -230,10 +232,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;
@@ -417,7 +419,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 "heap";
@@ -432,7 +434,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];
@@ -499,7 +501,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;
@@ -547,7 +549,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;
@@ -600,7 +602,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)
 {
 	Buffer		buf;
@@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called)
  *
  * Persistence change for the local sequence Relation.
  */
-void
+static void
 seq_local_change_persistence(Relation rel, char newrelpersistence)
 {
 	Buffer		buf;
@@ -632,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 8b5303553702..f30317d1feae 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..dd1a60d827a4
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2024, 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/xact.h"
+#include "access/sequenceam.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 10f43c51c5af..9ff8e062df0c 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1481,9 +1481,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 58ed9d216cc0..e44633d13b60 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 15ea0e24970c..fc2c18df4dc3 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -152,6 +152,7 @@ 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;
@@ -173,7 +174,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)
@@ -241,7 +242,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 */
@@ -297,7 +298,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
 
 	/* Read sequence data */
-	seq_local_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -310,7 +311,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 */
@@ -353,7 +354,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);
 }
@@ -470,8 +471,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;
@@ -625,7 +626,7 @@ do_setval(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);
 }
@@ -1353,7 +1354,7 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		bool		is_called;
 		int64		last_value;
 
-		seq_local_get_state(seqrel, &last_value, &is_called);
+		sequence_get_state(seqrel, &last_value, &is_called);
 
 		values[0] = Int64GetDatum(last_value);
 		values[1] = BoolGetDatum(is_called);
@@ -1400,7 +1401,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		seq_local_get_state(seqrel, &result, &is_called);
+		sequence_get_state(seqrel, &result, &is_called);
 	}
 
 	sequence_close(seqrel, NoLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 96c3a9eb086f..fdc56da31989 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"
@@ -1025,14 +1026,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)
 	{
@@ -1045,6 +1050,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 9ecddb142314..0c79454f641e 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 50f53159d581..777562ef7f43 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -380,6 +380,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
@@ -4939,23 +4940,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;
@@ -4992,6 +4996,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); }
 		;
@@ -5995,6 +6004,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 62015431fdf1..9ba0febe63c4 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"
@@ -518,6 +519,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 317a1f2b282f..68f160dda7d3 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 559ba9cdb2cd..c00642894ec0 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);
 
@@ -1831,17 +1835,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.
@@ -1872,6 +1868,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
  *
@@ -3711,14 +3750,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
@@ -4343,13 +4385,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);
@@ -6425,8 +6475,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);
@@ -6438,6 +6490,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_tables.c b/src/backend/utils/misc/guc_tables.c
index f04bfedb2fd1..a57509e421bf 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"
@@ -4341,6 +4342,17 @@ struct config_string ConfigureNamesString[] =
 		check_default_table_access_method, NULL, NULL
 	},
 
+	{
+		{"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets the default sequence access method for new sequences."),
+			NULL,
+			GUC_IS_NAME
+		},
+		&default_sequence_access_method,
+		DEFAULT_SEQUENCE_ACCESS_METHOD,
+		check_default_sequence_access_method, NULL, NULL
+	},
+
 	{
 		{"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default tablespace to create tables and indexes in."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 341f88adc87b..0104b4a9244e 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -750,6 +750,7 @@ autovacuum_worker_slots = 16	# autovacuum worker slots to allocate
 					#   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 dd25d2fe7b8a..846bbe051275 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 2c0b4f28c14d..ec68d85d8bcb 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2568,7 +2568,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");
@@ -3611,7 +3611,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 20bf9ea9cdf7..20b3d8520315 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1943,6 +1943,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 cf48ae6d0c2e..2b7d6c69e1e1 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5127,31 +5127,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
@@ -5176,32 +5178,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 dd0c52ab08b5..c3218a36bc48 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 2fb3a8528781..e60eeab33c02 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 c94dd83d3061..f1b6cd1091ef 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 32d6e718adca..65ad6ad6774c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2681,6 +2681,7 @@ SeqScanState
 SeqTable
 SeqTableData
 SeqType
+SequenceAmRoutine
 SequenceItem
 SerCommitSeqNo
 SerialControl
@@ -3750,6 +3751,7 @@ list_sort_comparator
 loc_chunk
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 local_ts_iter
 local_ts_radix_tree
@@ -4037,7 +4039,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
@@ -4264,6 +4265,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
@@ -4275,7 +4277,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.49.0

v15-0005-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From a30b389ab8bb36563a86069b90ef266ff5c7358b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 20 Jun 2025 13:46:33 +0900
Subject: [PATCH v15 5/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 af0007fb6d2f..62d86d69db45 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
@@ -190,6 +191,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 197c1295d93f..c0fd51dfa5f4 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -181,6 +181,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;
@@ -1246,6 +1247,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;
@@ -2405,6 +2407,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 */
 
@@ -2672,6 +2675,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);
@@ -2776,6 +2780,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);
 
@@ -3462,6 +3469,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;
 
@@ -3624,6 +3634,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.
  */
@@ -3833,6 +3894,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);
 
@@ -4389,6 +4451,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);
@@ -5128,6 +5192,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -5187,6 +5252,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 365073b3eae4..89b5cec22ac1 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;
@@ -404,6 +407,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 db944ec22307..c158c379061b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -134,6 +134,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
@@ -495,6 +496,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},
@@ -1182,6 +1184,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;
@@ -1303,6 +1306,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 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"));
@@ -14104,6 +14108,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;
@@ -18512,26 +18519,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);
@@ -18551,6 +18572,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);
@@ -18622,6 +18647,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);
 	}
@@ -18744,6 +18770,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 3cbcad65c5fb..4a98c3843ae2 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -93,6 +93,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;
@@ -165,6 +166,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},
@@ -467,6 +469,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)
@@ -742,6 +746,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 6ef789cb06d6..d6cc3b6a943d 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -98,6 +98,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;
@@ -154,6 +155,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},
@@ -459,6 +461,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;
@@ -702,6 +705,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(_("  --section=SECTION            restore named section (pre-data, data, or post-data)\n"));
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 386e21e0c596..764a183857f0 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -650,6 +650,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',
+			'--with-statistics',
+			'postgres',
+		],
+	},
 	no_table_access_method => {
 		dump_cmd => [
 			'pg_dump', '--no-sync',
@@ -881,6 +890,7 @@ my %full_runs = (
 	no_policies => 1,
 	no_privs => 1,
 	no_statistics => 1,
+	no_sequence_access_method => 1,
 	no_table_access_method => 1,
 	pg_dumpall_dbprivs => 1,
 	pg_dumpall_exclude => 1,
@@ -4832,6 +4842,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
@@ -4860,6 +4882,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 0d9270116549..7f7c4fbb4386 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1162,6 +1162,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 8ca68da5a556..927edb97fedc 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -598,6 +598,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 b649bd3a5ae0..f90ede06a2cd 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -820,6 +820,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.49.0

v15-0006-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From 97a7465b51cdfdf5419f010dfb2cbcfca3f59820 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v15 6/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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 b265cc89c9d4..505506b93553 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9754,6 +9754,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 fef9584f908e..a80de08dd26e 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -95,6 +95,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 af476c82fcc1..9f369cc89930 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..3067dc4d4df0 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>SEQUENCE</literal> and
+      <literal>INDEX</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 1e283f13d15c..52c6096e4ba2 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..a96170bfac03
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.49.0

v15-0007-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From 9eb9a8cad7b96bc1b93506750b9b44e7518820c6 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 26 Apr 2024 15:09:00 +0900
Subject: [PATCH v15 7/7] snowflake: Add sequence AM based on it

This includes documentation and a basic implementation, though this is
in a very early stage.
---
 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            | 570 +++++++++++++++++++++++
 contrib/snowflake/snowflake.control      |   5 +
 contrib/snowflake/sql/snowflake.sql      |  29 ++
 13 files changed, 858 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 a80de08dd26e..bee964856ece 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -159,6 +159,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 ed30ee7d639f..e3bd2b464172 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..567669eea790
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2024, 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 += bloom
+
+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..be4c4039ecd0
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,570 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2024, 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/sequenceam.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 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(snowflake_magic));
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+	sm->magic = SNOWFLAKE_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 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, (Item) 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)
+{
+	Page		page;
+	ItemId		lp;
+	snowflake_magic *sm;
+	Form_snowflake_data	seq;
+
+	*buf = ReadBuffer(rel, 0);
+	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(*buf);
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+
+	if (sm->magic != SNOWFLAKE_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_snowflake_data) GETSTRUCT(seqdatatuple);
+
+	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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_snowflake_data seq;
+
+	seq = read_snowflake(rel, &buf, &seqdatatuple);
+	*last_value = seq->count;
+	*is_called = seq->is_called;
+	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.49.0

#35Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#34)
7 attachment(s)
Re: Sequence Access Methods, round two

On Fri, Jun 20, 2025 at 01:50:48PM +0900, Michael Paquier wrote:

Rebased as v15, following a report from the CI that the new pg_dump
command in the TAP tests needed a --with-statistics switch, like its
relatives.

v16 rebased, conflict with naming of pg_dump option --statistics vs
--with-statistics.
--
Michael

Attachments:

v16-0006-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From a68ca63a072f7c20287f7cff100b72ef6b71bcd4 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v16 6/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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 20ccb2d6b544..4a892d946e64 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9761,6 +9761,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 af476c82fcc1..9f369cc89930 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..3067dc4d4df0 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>SEQUENCE</literal> and
+      <literal>INDEX</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 1e283f13d15c..52c6096e4ba2 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..a96170bfac03
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.50.0

v16-0007-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From cd64ab3fe8dffe16f306ae5605ae2bcbccdd5f71 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 26 Apr 2024 15:09:00 +0900
Subject: [PATCH v16 7/7] snowflake: Add sequence AM based on it

This includes documentation and a basic implementation, though this is
in a very early stage.
---
 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            | 570 +++++++++++++++++++++++
 contrib/snowflake/snowflake.control      |   5 +
 contrib/snowflake/sql/snowflake.sql      |  29 ++
 13 files changed, 858 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 ed30ee7d639f..e3bd2b464172 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..567669eea790
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2024, 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 += bloom
+
+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..be4c4039ecd0
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,570 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2024, 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/sequenceam.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 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(snowflake_magic));
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+	sm->magic = SNOWFLAKE_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 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, (Item) 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)
+{
+	Page		page;
+	ItemId		lp;
+	snowflake_magic *sm;
+	Form_snowflake_data	seq;
+
+	*buf = ReadBuffer(rel, 0);
+	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(*buf);
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+
+	if (sm->magic != SNOWFLAKE_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_snowflake_data) GETSTRUCT(seqdatatuple);
+
+	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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_snowflake_data seq;
+
+	seq = read_snowflake(rel, &buf, &seqdatatuple);
+	*last_value = seq->count;
+	*is_called = seq->is_called;
+	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.50.0

v16-0001-Remove-FormData_pg_sequence_data-from-init_param.patchtext/x-diff; charset=us-asciiDownload
From 5e2ed17a2938628d360620c82fae161a570ecb52 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:00:45 +0900
Subject: [PATCH v16 1/7] Remove FormData_pg_sequence_data from
 init_params()/sequence.c

init_params() sets up "last_value" and "is_called" for a sequence, based
on the sequence properties in pg_sequences.  This simplifies the logic
around log_cnt, which is reset to 0 when the metadata of a sequence is
expected to start from afresh when its properties are updated.
---
 src/backend/commands/sequence.c | 81 ++++++++++++++++++++-------------
 1 file changed, 49 insertions(+), 32 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 451ae6f7f694..08744c3e9112 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -106,7 +106,9 @@ static Form_pg_sequence_data read_seq_tuple(Relation rel,
 static void init_params(ParseState *pstate, List *options, bool for_identity,
 						bool isInit,
 						Form_pg_sequence seqform,
-						Form_pg_sequence_data seqdataform,
+						int64 *last_value,
+						bool *reset_state,
+						bool *is_called,
 						bool *need_seq_rewrite,
 						List **owned_by);
 static void do_setval(Oid relid, int64 next, bool iscalled);
@@ -121,7 +123,9 @@ ObjectAddress
 DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 {
 	FormData_pg_sequence seqform;
-	FormData_pg_sequence_data seqdataform;
+	int64		last_value;
+	bool		reset_state;
+	bool		is_called;
 	bool		need_seq_rewrite;
 	List	   *owned_by;
 	CreateStmt *stmt = makeNode(CreateStmt);
@@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 
 	/* Check and set all option values */
 	init_params(pstate, seq->options, seq->for_identity, true,
-				&seqform, &seqdataform,
+				&seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/*
@@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 		{
 			case SEQ_COL_LASTVAL:
 				coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
-				value[i - 1] = Int64GetDatumFast(seqdataform.last_value);
+				value[i - 1] = Int64GetDatumFast(last_value);
 				break;
 			case SEQ_COL_LOG:
 				coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
@@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	ObjectAddress address;
 	Relation	rel;
 	HeapTuple	seqtuple;
+	bool		reset_state = false;
+	bool		is_called;
+	int64		last_value;
 	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
@@ -481,12 +488,14 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	/* 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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
-				seqform, newdataform,
+				seqform, &last_value, &reset_state, &is_called,
 				&need_seq_rewrite, &owned_by);
 
 	/* If needed, rewrite the sequence relation itself */
@@ -513,6 +522,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 		/*
 		 * 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);
 	}
 
@@ -1236,17 +1249,19 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 /*
  * init_params: process the options list of CREATE or ALTER SEQUENCE, and
  * store the values into appropriate fields of seqform, for changes that go
- * into the pg_sequence catalog, and fields of seqdataform for changes to the
- * sequence relation itself.  Set *need_seq_rewrite to true if we changed any
- * parameters that require rewriting the sequence's relation (interesting for
- * ALTER SEQUENCE).  Also set *owned_by to any OWNED BY option, or to NIL if
- * there is none.
+ * into the pg_sequence catalog, and fields for changes to the sequence
+ * relation itself (is_called, last_value or any state it may hold).  Set
+ * *need_seq_rewrite to true if we changed any parameters that require
+ * rewriting the sequence's relation (interesting for ALTER SEQUENCE).  Also
+ * set *owned_by to any OWNED BY option, or to NIL if there is none.  Set
+ * *reset_state if the internal state of the sequence needs to change on a
+ * follow-up nextval().
  *
  * If isInit is true, fill any unspecified options with default values;
  * otherwise, do not change existing options that aren't explicitly overridden.
  *
  * Note: we force a sequence rewrite whenever we change parameters that affect
- * generation of future sequence values, even if the seqdataform per se is not
+ * generation of future sequence values, even if the metadata per se is not
  * changed.  This allows ALTER SEQUENCE to behave transactionally.  Currently,
  * the only option that doesn't cause that is OWNED BY.  It's *necessary* for
  * ALTER SEQUENCE OWNED BY to not rewrite the sequence, because that would
@@ -1257,7 +1272,9 @@ static void
 init_params(ParseState *pstate, List *options, bool for_identity,
 			bool isInit,
 			Form_pg_sequence seqform,
-			Form_pg_sequence_data seqdataform,
+			int64 *last_value,
+			bool *reset_state,
+			bool *is_called,
 			bool *need_seq_rewrite,
 			List **owned_by)
 {
@@ -1363,11 +1380,11 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	}
 
 	/*
-	 * We must reset log_cnt when isInit or when changing any parameters that
-	 * would affect future nextval allocations.
+	 * We must reset the state when isInit or when changing any parameters
+	 * that would affect future nextval allocations.
 	 */
 	if (isInit)
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 
 	/* AS type */
 	if (as_type != NULL)
@@ -1416,7 +1433,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("INCREMENT must not be zero")));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1428,7 +1445,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	{
 		seqform->seqcycle = boolVal(is_cycled->arg);
 		Assert(BoolIsValid(seqform->seqcycle));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
@@ -1439,7 +1456,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (max_value != NULL && max_value->arg)
 	{
 		seqform->seqmax = defGetInt64(max_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || max_value != NULL || reset_max_value)
 	{
@@ -1455,7 +1472,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmax = -1;	/* descending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate maximum value.  No need to check INT8 as seqmax is an int64 */
@@ -1471,7 +1488,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (min_value != NULL && min_value->arg)
 	{
 		seqform->seqmin = defGetInt64(min_value);
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit || min_value != NULL || reset_min_value)
 	{
@@ -1487,7 +1504,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 		}
 		else
 			seqform->seqmin = 1;	/* ascending seq */
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 
 	/* Validate minimum value.  No need to check INT8 as seqmin is an int64 */
@@ -1538,30 +1555,30 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 	if (restart_value != NULL)
 	{
 		if (restart_value->arg != NULL)
-			seqdataform->last_value = defGetInt64(restart_value);
+			*last_value = defGetInt64(restart_value);
 		else
-			seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
-		seqdataform->log_cnt = 0;
+			*last_value = seqform->seqstart;
+		*is_called = false;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-		seqdataform->last_value = seqform->seqstart;
-		seqdataform->is_called = false;
+		*last_value = seqform->seqstart;
+		*is_called = false;
 	}
 
 	/* crosscheck RESTART (or current value, if changing MIN/MAX) */
-	if (seqdataform->last_value < seqform->seqmin)
+	if (*last_value < seqform->seqmin)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%" PRId64 ") cannot be less than MINVALUE (%" PRId64 ")",
-						seqdataform->last_value,
+						*last_value,
 						seqform->seqmin)));
-	if (seqdataform->last_value > seqform->seqmax)
+	if (*last_value > seqform->seqmax)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("RESTART value (%" PRId64 ") cannot be greater than MAXVALUE (%" PRId64 ")",
-						seqdataform->last_value,
+						*last_value,
 						seqform->seqmax)));
 
 	/* CACHE */
@@ -1573,7 +1590,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("CACHE (%" PRId64 ") must be greater than zero",
 							seqform->seqcache)));
-		seqdataform->log_cnt = 0;
+		*reset_state = true;
 	}
 	else if (isInit)
 	{
-- 
2.50.0

v16-0002-Integrate-addition-of-attributes-for-sequences-w.patchtext/x-diff; charset=us-asciiDownload
From d19cf538d071d912ced831eb0877064ada162693 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v16 2/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               | 29 +++++++++++++++++--
 src/backend/commands/tablecmds.c              | 10 +++++++
 src/backend/tcop/utility.c                    |  4 +++
 .../test_ddl_deparse/expected/alter_table.out | 10 +++++--
 .../expected/create_sequence_1.out            |  5 +++-
 .../expected/create_table.out                 | 15 ++++++++--
 .../test_ddl_deparse/test_ddl_deparse.c       |  3 ++
 8 files changed, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 86a236bd58b1..58ee44e2b617 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2412,6 +2412,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 08744c3e9112..20e8978c9346 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -136,6 +136,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;
@@ -174,7 +177,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;
@@ -198,7 +200,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;
@@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 c6dd2e020da2..b1cbb14c3d08 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4645,6 +4645,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 */
@@ -4940,6 +4941,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,
@@ -5372,6 +5380,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,
@@ -6587,6 +6596,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 4f4191b0ea6b..9e5c2ec2a14e 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1658,7 +1658,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 50d0354a3417..ed31059ef584 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
@@ -70,7 +73,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_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
index 5837ea484e40..310ce5a6baf5 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.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 193669f2bc1e..254fdf90c79a 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.50.0

v16-0003-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From 1d59359a21aa207cc28039ce1c633aa624ba1bd4 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 30 Apr 2025 08:26:43 +0900
Subject: [PATCH v16 3/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.
- seqlocalxlog.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/seqlocalam.h               |  58 ++
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 .../rmgrdesc/{seqdesc.c => seqlocaldesc.c}    |  20 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/meson.build       |   2 +
 src/backend/access/sequence/seqlocalam.c      | 634 ++++++++++++++++++
 src/backend/access/sequence/seqlocalxlog.c    |  82 +++
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 634 +-----------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 14 files changed, 822 insertions(+), 622 deletions(-)
 create mode 100644 src/include/access/seqlocalam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%)
 create mode 100644 src/backend/access/sequence/seqlocalam.c
 create mode 100644 src/backend/access/sequence/seqlocalxlog.c

diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 8e7fc9db8778..b942b25350bc 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/seqlocalam.h b/src/include/access/seqlocalam.h
new file mode 100644
index 000000000000..225fb9a2cbeb
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2024, 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 "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_SEQ_LOCAL_LOG			0x00
+
+typedef struct xl_seq_local_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_seq_local_rec;
+
+/*
+ * 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;
+
+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);
+
+/* 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);
+extern void seq_local_change_persistence(Relation rel,
+										 char newrelpersistence);
+
+#endif							/* SEQLOCALAM_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 96c98e800c22..6b3b3e1e44c1 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 0d289d77fcf7..6ffbcb2c4735 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.c
  *
  * Portions Copyright (c) 1996-2025, 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.h"
+#include "access/seqlocalam.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..a15ceec1c0a0 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 ec9ab9b7e9db..8bc0e95e68c0 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2025, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'seqlocalam.c',
+  'seqlocalxlog.c',
   'sequence.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
new file mode 100644
index 000000000000..e019a6f5a95d
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -0,0 +1,634 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.c
+ *	  Local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, 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/multixact.h"
+#include "access/seqlocalam.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"
+
+
+/*
+ * 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
+
+/* 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, (Item) 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 "heap";
+}
+
+/*
+ * 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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_seq_local_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	*last_value = seq->last_value;
+	*is_called = seq->is_called;
+
+	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/sequence/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c
new file mode 100644
index 000000000000..db0ad969dbf2
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalxlog.c
@@ -0,0 +1,82 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalxlog.c
+ *	  WAL replay logic for local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/access/sequence/seqlocalxlog.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/seqlocalam.h"
+#include "access/xlogutils.h"
+#include "storage/block.h"
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+seq_local_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+seq_local_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record);
+	seq_local_magic *sm;
+
+	if (info != XLOG_SEQ_LOCAL_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = (Page) BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	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_local_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec);
+
+	if (PageAddItem(localpage, (Item) item, itemsz,
+					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "seq_local_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 1b7499726eb0..cc92268937b1 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 20e8978c9346..15ea0e24970c 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -50,23 +51,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -96,13 +80,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,
@@ -134,14 +114,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
@@ -174,35 +148,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;
@@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	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)
@@ -292,10 +222,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;
@@ -306,7 +232,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))
@@ -315,40 +240,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 */
@@ -357,106 +250,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, (Item) 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
  *
@@ -468,10 +261,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;
@@ -480,7 +270,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	bool		reset_state = false;
 	bool		is_called;
 	int64		last_value;
-	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
 	relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -507,16 +296,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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -526,32 +307,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 */
@@ -580,8 +339,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
@@ -596,10 +353,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);
 }
@@ -662,24 +416,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);
@@ -724,105 +469,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;
@@ -832,69 +481,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;
@@ -984,9 +570,6 @@ do_setval(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,
@@ -1020,9 +603,6 @@ do_setval(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),
@@ -1044,37 +624,8 @@ do_setval(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);
 }
@@ -1215,62 +766,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
@@ -1855,16 +1350,13 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		Buffer		buf;
-		HeapTupleData seqtuple;
-		Form_pg_sequence_data seq;
+		bool		is_called;
+		int64		last_value;
 
-		seq = read_seq_tuple(seqrel, &buf, &seqtuple);
+		seq_local_get_state(seqrel, &last_value, &is_called);
 
-		values[0] = Int64GetDatum(seq->last_value);
-		values[1] = BoolGetDatum(seq->is_called);
-
-		UnlockReleaseBuffer(buf);
+		values[0] = Int64GetDatum(last_value);
+		values[1] = BoolGetDatum(is_called);
 	}
 	else
 		memset(isnull, true, sizeof(isnull));
@@ -1908,17 +1400,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);
 	}
+
 	sequence_close(seqrel, NoLock);
 
 	if (is_called)
@@ -1927,57 +1411,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
-
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = (Page) BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, (Item) item, itemsz,
-					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1992,14 +1425,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c767e..8d1195de2637 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/seqlocaldesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index fac509ed134e..2fcf9fc4392a 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index f26d75e01cfd..2495a28d26a1 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.50.0

v16-0004-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From 77758840a6f232a41393f07eb3e303e97c17812b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 4 Dec 2024 08:09:46 +0900
Subject: [PATCH v16 4/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               |  15 --
 src/include/access/sequenceam.h               | 181 ++++++++++++++++++
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/seqlocalam.c      |  41 +++-
 src/backend/access/sequence/sequence.c        |   3 +-
 src/backend/access/sequence/sequenceamapi.c   | 145 ++++++++++++++
 src/backend/catalog/heap.c                    |   6 +-
 src/backend/commands/amcmds.c                 |  16 ++
 src/backend/commands/sequence.c               |  23 +--
 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_tables.c           |  12 ++
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/bin/psql/describe.c                       |   2 +
 src/bin/psql/tab-complete.in.c                |   4 +-
 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 +-
 40 files changed, 695 insertions(+), 182 deletions(-)
 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
index 225fb9a2cbeb..21936511ac2b 100644
--- a/src/include/access/seqlocalam.h
+++ b/src/include/access/seqlocalam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_SEQ_LOCAL_LOG			0x00
@@ -41,18 +40,4 @@ 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);
 
-/* 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);
-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..ac48c8b468be
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,181 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2024, 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.
+	 */
+	void		(*get_state) (Relation rel, int64 *last_value, bool *is_called);
+
+	/*
+	 * 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)
+{
+	rel->rd_sequenceam->get_state(rel, last_value, is_called);
+}
+
+/*
+ * 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 26d15928a155..8f076fcec958 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 6e98a0930c27..080bea5031a9 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 07d182da796a..012a2863c037 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 118d6da1ace0..b272b4885e6e 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',
@@ -7894,6 +7900,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 cb730aeac864..d0054c5c988c 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 dd22b5efdfd9..6790728aced3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -145,6 +145,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 9ac0b67683d3..7693e9941fc9 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index ea36cb0fda40..c0311fbe8220 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 58ee44e2b617..9d2bd341d104 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3224,6 +3224,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 82ac8646a8d4..ab5e2eab0401 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 b552359915f1..4e418968253d 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 a15ceec1c0a0..62006165a15f 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = seqlocalam.o seqlocalxlog.o sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 8bc0e95e68c0..d82af34d538c 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -4,4 +4,5 @@ backend_sources += files(
   'seqlocalam.c',
   'seqlocalxlog.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
index e019a6f5a95d..5dec9d51ec82 100644
--- a/src/backend/access/sequence/seqlocalam.c
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -17,6 +17,7 @@
 
 #include "access/multixact.h"
 #include "access/seqlocalam.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -24,6 +25,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -230,10 +232,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;
@@ -417,7 +419,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 "heap";
@@ -432,7 +434,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];
@@ -499,7 +501,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;
@@ -547,7 +549,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;
@@ -600,7 +602,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)
 {
 	Buffer		buf;
@@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called)
  *
  * Persistence change for the local sequence Relation.
  */
-void
+static void
 seq_local_change_persistence(Relation rel, char newrelpersistence)
 {
 	Buffer		buf;
@@ -632,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 8b5303553702..f30317d1feae 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..dd1a60d827a4
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2024, 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/xact.h"
+#include "access/sequenceam.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 fd6537567ea2..e774dfa86e4e 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 58ed9d216cc0..e44633d13b60 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 15ea0e24970c..fc2c18df4dc3 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -152,6 +152,7 @@ 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;
@@ -173,7 +174,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)
@@ -241,7 +242,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 */
@@ -297,7 +298,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
 
 	/* Read sequence data */
-	seq_local_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -310,7 +311,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 */
@@ -353,7 +354,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);
 }
@@ -470,8 +471,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;
@@ -625,7 +626,7 @@ do_setval(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);
 }
@@ -1353,7 +1354,7 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		bool		is_called;
 		int64		last_value;
 
-		seq_local_get_state(seqrel, &last_value, &is_called);
+		sequence_get_state(seqrel, &last_value, &is_called);
 
 		values[0] = Int64GetDatum(last_value);
 		values[1] = BoolGetDatum(is_called);
@@ -1400,7 +1401,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		seq_local_get_state(seqrel, &result, &is_called);
+		sequence_get_state(seqrel, &result, &is_called);
 	}
 
 	sequence_close(seqrel, NoLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b1cbb14c3d08..c61dd48f2a18 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"
@@ -1025,14 +1026,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)
 	{
@@ -1045,6 +1050,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 9ecddb142314..0c79454f641e 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 db43034b9db5..21188cef3ad8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -381,6 +381,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
@@ -4981,23 +4982,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;
@@ -5034,6 +5038,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); }
 		;
@@ -6037,6 +6046,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 afcf54169c3b..a8fb08752f91 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"
@@ -518,6 +519,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 317a1f2b282f..68f160dda7d3 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 6fe268a8eec1..f3fd8d26b185 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);
 
@@ -1831,17 +1835,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.
@@ -1872,6 +1868,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
  *
@@ -3711,14 +3750,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
@@ -4343,13 +4385,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);
@@ -6425,8 +6475,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);
@@ -6438,6 +6490,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_tables.c b/src/backend/utils/misc/guc_tables.c
index d14b1678e7fe..f225c85aa196 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"
@@ -4341,6 +4342,17 @@ struct config_string ConfigureNamesString[] =
 		check_default_table_access_method, NULL, NULL
 	},
 
+	{
+		{"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets the default sequence access method for new sequences."),
+			NULL,
+			GUC_IS_NAME
+		},
+		&default_sequence_access_method,
+		DEFAULT_SEQUENCE_ACCESS_METHOD,
+		check_default_sequence_access_method, NULL, NULL
+	},
+
 	{
 		{"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default tablespace to create tables and indexes in."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index a9d8293474af..af0c0886adcc 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -750,6 +750,7 @@ autovacuum_worker_slots = 16	# autovacuum worker slots to allocate
 					#   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 7a06af48842d..d6d2a5940862 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 8b10f2313f39..8ac13b26fe6a 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2596,7 +2596,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");
@@ -3663,7 +3663,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 20bf9ea9cdf7..20b3d8520315 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1943,6 +1943,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 a79325e8a2f7..19ade0eb7e62 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5129,31 +5129,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
@@ -5178,32 +5180,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 943e56506bf1..f36e267c4b98 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 2fb3a8528781..e60eeab33c02 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 df795759bb4c..dbaceb9fd2c8 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 e6f2e93b2d6f..4bfb6434fe6f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2691,6 +2691,7 @@ SeqScanState
 SeqTable
 SeqTableData
 SeqType
+SequenceAmRoutine
 SequenceItem
 SerCommitSeqNo
 SerialControl
@@ -3764,6 +3765,7 @@ list_sort_comparator
 loc_chunk
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 local_ts_iter
 local_ts_radix_tree
@@ -4051,7 +4053,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
@@ -4278,6 +4279,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
@@ -4289,7 +4291,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.50.0

v16-0005-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From a42434c85c5d92080fdfa2fc0e2f3769f6a70d49 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 15 Aug 2025 14:35:50 +0900
Subject: [PATCH v16 5/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 3c3acbaccdb5..cf7a90ee3db2 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;
@@ -2420,6 +2422,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 */
 
@@ -2687,6 +2690,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);
@@ -2791,6 +2795,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);
 
@@ -3500,6 +3507,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;
 
@@ -3662,6 +3672,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.
  */
@@ -3871,6 +3932,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);
 
@@ -4391,6 +4453,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);
@@ -5130,6 +5194,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -5189,6 +5254,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 fc7a66391633..b919641b9080 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},
@@ -1232,6 +1234,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;
@@ -1354,6 +1357,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 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"));
@@ -14341,6 +14345,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;
@@ -18824,26 +18831,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);
@@ -18863,6 +18884,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);
@@ -18934,6 +18959,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);
 	}
@@ -19056,6 +19082,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 bb451c1bae14..2d52d96514d6 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},
@@ -450,6 +452,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)
@@ -731,6 +735,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 c9776306c5c4..b2e25da92b92 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},
@@ -435,6 +437,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;
@@ -566,6 +569,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 e7a2d64f7413..e9f719460400 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -650,6 +650,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_table_access_method => {
 		dump_cmd => [
 			'pg_dump', '--no-sync',
@@ -873,6 +882,7 @@ my %full_runs = (
 	no_policies => 1,
 	no_privs => 1,
 	no_statistics => 1,
+	no_sequence_access_method => 1,
 	no_table_access_method => 1,
 	pg_dumpall_dbprivs => 1,
 	pg_dumpall_exclude => 1,
@@ -4911,6 +4921,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
@@ -4939,6 +4961,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 fd4ecf01a0a0..895e324ba193 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 9f639f61db02..94a5bad771b2 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.50.0

#36Kirill Reshke
reshkekirill@gmail.com
In reply to: Michael Paquier (#35)
Re: Sequence Access Methods, round two

On Fri, 15 Aug 2025 at 10:47, Michael Paquier <michael@paquier.xyz> wrote:

On Fri, Jun 20, 2025 at 01:50:48PM +0900, Michael Paquier wrote:

Rebased as v15, following a report from the CI that the new pg_dump
command in the TAP tests needed a --with-statistics switch, like its
relatives.

v16 rebased, conflict with naming of pg_dump option --statistics vs
--with-statistics.
--
Michael

Hi!

0001 - LGTM?

0002. WFM, the sole observation I have is that we incorporate sequence
logic to AlterTable utilities, making it less clear of what they
actually do.
I see we already have code path handling CREATE VIEW (in
AlterTableGetLockLevel for example), so maybe it is worth renaming
AlterTable* to AlterRelation*?
It would be really invasive refactoring though, so maybe we should
ignore this slight inconsistency in function names and what they
actually do.
Otherwise LGTM.

I did not review the new 0003-0007 series. Will try to find the time
to do this.

--
Best regards,
Kirill Reshke

#37Michael Paquier
michael@paquier.xyz
In reply to: Kirill Reshke (#36)
Re: Sequence Access Methods, round two

On Fri, Aug 15, 2025 at 12:36:00PM +0500, Kirill Reshke wrote:

0001 - LGTM?

Thanks for the review. Yes, I am eyeing at applying this refactoring
piece to limit the footprint of Form_pg_sequence_data. The
expectations of reset_state are not completely documented at the top
of init_params(), because it is not only about nextval(). I'll try to
think about a more careful wording.

0002. WFM, the sole observation I have is that we incorporate sequence
logic to AlterTable utilities, making it less clear of what they
actually do.

Thanks, I am also looking at applying this part of the patch set.
There is a second reason for that: we don't track yet what kind of
attributes are created within a CREATE SEQUENCE, so ADD COLUMN TO
SEQUENCE is IMO also useful on its own to make user-facing some of the
actions taken internally by the backend in this case. Perhaps there's
an argument that people do not like the AddColumnToView layer, and
that we should not expand that to sequences, but I like this new
separation concept for the deparsing logistics.

I see we already have code path handling CREATE VIEW (in
AlterTableGetLockLevel for example), so maybe it is worth renaming
AlterTable* to AlterRelation*?
It would be really invasive refactoring though, so maybe we should
ignore this slight inconsistency in function names and what they
actually do.

Some of these AlterTable* sub-structures are also shared for some code
paths of view, matviews and sequences, if I recall correctly. I'm
used to this code using AlterTable things, so perhaps my opinion is
just biased based on that.
--
Michael

#38Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#37)
6 attachment(s)
Re: Sequence Access Methods, round two

On Sat, Aug 16, 2025 at 09:22:18AM +0900, Michael Paquier wrote:

On Fri, Aug 15, 2025 at 12:36:00PM +0500, Kirill Reshke wrote:

0001 - LGTM?

Thanks for the review. Yes, I am eyeing at applying this refactoring
piece to limit the footprint of Form_pg_sequence_data. The
expectations of reset_state are not completely documented at the top
of init_params(), because it is not only about nextval(). I'll try to
think about a more careful wording.

Applied this one after more tweaking.

0002. WFM, the sole observation I have is that we incorporate sequence
logic to AlterTable utilities, making it less clear of what they
actually do.

Thanks, I am also looking at applying this part of the patch set.
There is a second reason for that: we don't track yet what kind of
attributes are created within a CREATE SEQUENCE, so ADD COLUMN TO
SEQUENCE is IMO also useful on its own to make user-facing some of the
actions taken internally by the backend in this case. Perhaps there's
an argument that people do not like the AddColumnToView layer, and
that we should not expand that to sequences, but I like this new
separation concept for the deparsing logistics.

For this one, I'm having some second thoughts after re-reading Tomas
point upthread, which is very close to your point. It is true that
using AlterColumn is an implementation detail of sequences, because we
store some of their data as heap. Using AddColumnToSequence feels
indeed a bit out of place after more consideration. We could just
reuse AlterColumn, saving from the extra sub-command. What do you
think?

Rebased the rest as attached, for now.
--
Michael

Attachments:

v17-0001-Integrate-addition-of-attributes-for-sequences-w.patchtext/x-diff; charset=us-asciiDownload
From 78aec00142d82bc8247f77efc77fd84f27cabdd0 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v17 1/6] 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               | 29 +++++++++++++++++--
 src/backend/commands/tablecmds.c              | 10 +++++++
 src/backend/tcop/utility.c                    |  4 +++
 .../test_ddl_deparse/expected/alter_table.out | 10 +++++--
 .../expected/create_sequence_1.out            |  5 +++-
 .../expected/create_table.out                 | 15 ++++++++--
 .../test_ddl_deparse/test_ddl_deparse.c       |  3 ++
 8 files changed, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 86a236bd58b1..58ee44e2b617 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2412,6 +2412,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 a3c8cff97b03..9569989f052a 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -136,6 +136,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;
@@ -174,7 +177,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;
@@ -198,7 +200,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;
@@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 c6dd2e020da2..b1cbb14c3d08 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4645,6 +4645,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 */
@@ -4940,6 +4941,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,
@@ -5372,6 +5380,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,
@@ -6587,6 +6596,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 4f4191b0ea6b..9e5c2ec2a14e 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1658,7 +1658,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 50d0354a3417..ed31059ef584 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
@@ -70,7 +73,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_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
index 5837ea484e40..310ce5a6baf5 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.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 193669f2bc1e..254fdf90c79a 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.50.0

v17-0002-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From 33b60585c4e566699c614fa230797ef684c13971 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 30 Apr 2025 08:26:43 +0900
Subject: [PATCH v17 2/6] 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.
- seqlocalxlog.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/seqlocalam.h               |  58 ++
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 .../rmgrdesc/{seqdesc.c => seqlocaldesc.c}    |  20 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/meson.build       |   2 +
 src/backend/access/sequence/seqlocalam.c      | 634 ++++++++++++++++++
 src/backend/access/sequence/seqlocalxlog.c    |  82 +++
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 634 +-----------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 14 files changed, 822 insertions(+), 622 deletions(-)
 create mode 100644 src/include/access/seqlocalam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%)
 create mode 100644 src/backend/access/sequence/seqlocalam.c
 create mode 100644 src/backend/access/sequence/seqlocalxlog.c

diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 8e7fc9db8778..b942b25350bc 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/seqlocalam.h b/src/include/access/seqlocalam.h
new file mode 100644
index 000000000000..225fb9a2cbeb
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2024, 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 "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_SEQ_LOCAL_LOG			0x00
+
+typedef struct xl_seq_local_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_seq_local_rec;
+
+/*
+ * 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;
+
+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);
+
+/* 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);
+extern void seq_local_change_persistence(Relation rel,
+										 char newrelpersistence);
+
+#endif							/* SEQLOCALAM_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 96c98e800c22..6b3b3e1e44c1 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 0d289d77fcf7..6ffbcb2c4735 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.c
  *
  * Portions Copyright (c) 1996-2025, 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.h"
+#include "access/seqlocalam.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..a15ceec1c0a0 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 ec9ab9b7e9db..8bc0e95e68c0 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2025, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'seqlocalam.c',
+  'seqlocalxlog.c',
   'sequence.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
new file mode 100644
index 000000000000..e019a6f5a95d
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -0,0 +1,634 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.c
+ *	  Local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, 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/multixact.h"
+#include "access/seqlocalam.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"
+
+
+/*
+ * 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
+
+/* 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, (Item) 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 "heap";
+}
+
+/*
+ * 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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_seq_local_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	*last_value = seq->last_value;
+	*is_called = seq->is_called;
+
+	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/sequence/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c
new file mode 100644
index 000000000000..db0ad969dbf2
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalxlog.c
@@ -0,0 +1,82 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalxlog.c
+ *	  WAL replay logic for local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *        src/backend/access/sequence/seqlocalxlog.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/seqlocalam.h"
+#include "access/xlogutils.h"
+#include "storage/block.h"
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+seq_local_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+seq_local_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record);
+	seq_local_magic *sm;
+
+	if (info != XLOG_SEQ_LOCAL_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = (Page) BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	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_local_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec);
+
+	if (PageAddItem(localpage, (Item) item, itemsz,
+					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "seq_local_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 1b7499726eb0..cc92268937b1 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 9569989f052a..41cfa4f1b9a5 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -50,23 +51,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -96,13 +80,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,
@@ -134,14 +114,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
@@ -174,35 +148,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;
@@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	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)
@@ -292,10 +222,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;
@@ -306,7 +232,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))
@@ -315,40 +240,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 */
@@ -357,106 +250,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, (Item) 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
  *
@@ -468,10 +261,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;
@@ -480,7 +270,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	bool		reset_state = false;
 	bool		is_called;
 	int64		last_value;
-	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
 	relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -507,16 +296,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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -526,32 +307,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 */
@@ -580,8 +339,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
@@ -596,10 +353,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);
 }
@@ -662,24 +416,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);
@@ -724,105 +469,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;
@@ -832,69 +481,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;
@@ -984,9 +570,6 @@ do_setval(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,
@@ -1020,9 +603,6 @@ do_setval(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),
@@ -1044,37 +624,8 @@ do_setval(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);
 }
@@ -1215,62 +766,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
@@ -1855,16 +1350,13 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		Buffer		buf;
-		HeapTupleData seqtuple;
-		Form_pg_sequence_data seq;
+		bool		is_called;
+		int64		last_value;
 
-		seq = read_seq_tuple(seqrel, &buf, &seqtuple);
+		seq_local_get_state(seqrel, &last_value, &is_called);
 
-		values[0] = Int64GetDatum(seq->last_value);
-		values[1] = BoolGetDatum(seq->is_called);
-
-		UnlockReleaseBuffer(buf);
+		values[0] = Int64GetDatum(last_value);
+		values[1] = BoolGetDatum(is_called);
 	}
 	else
 		memset(isnull, true, sizeof(isnull));
@@ -1908,17 +1400,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);
 	}
+
 	sequence_close(seqrel, NoLock);
 
 	if (is_called)
@@ -1927,57 +1411,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
-
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = (Page) BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, (Item) item, itemsz,
-					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1992,14 +1425,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c767e..8d1195de2637 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/seqlocaldesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index fac509ed134e..2fcf9fc4392a 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index f26d75e01cfd..2495a28d26a1 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.50.0

v17-0003-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From 383387ee0811f8499de10a599fd4927006946e18 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 4 Dec 2024 08:09:46 +0900
Subject: [PATCH v17 3/6] 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               |  15 --
 src/include/access/sequenceam.h               | 181 ++++++++++++++++++
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/seqlocalam.c      |  41 +++-
 src/backend/access/sequence/sequence.c        |   3 +-
 src/backend/access/sequence/sequenceamapi.c   | 145 ++++++++++++++
 src/backend/catalog/heap.c                    |   6 +-
 src/backend/commands/amcmds.c                 |  16 ++
 src/backend/commands/sequence.c               |  23 +--
 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_tables.c           |  12 ++
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/bin/psql/describe.c                       |   2 +
 src/bin/psql/tab-complete.in.c                |   4 +-
 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 +-
 40 files changed, 695 insertions(+), 182 deletions(-)
 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
index 225fb9a2cbeb..21936511ac2b 100644
--- a/src/include/access/seqlocalam.h
+++ b/src/include/access/seqlocalam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_SEQ_LOCAL_LOG			0x00
@@ -41,18 +40,4 @@ 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);
 
-/* 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);
-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..ac48c8b468be
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,181 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2024, 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.
+	 */
+	void		(*get_state) (Relation rel, int64 *last_value, bool *is_called);
+
+	/*
+	 * 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)
+{
+	rel->rd_sequenceam->get_state(rel, last_value, is_called);
+}
+
+/*
+ * 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 26d15928a155..8f076fcec958 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 6e98a0930c27..080bea5031a9 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 07d182da796a..012a2863c037 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 118d6da1ace0..b272b4885e6e 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',
@@ -7894,6 +7900,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 cb730aeac864..d0054c5c988c 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 dd22b5efdfd9..6790728aced3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -145,6 +145,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 9ac0b67683d3..7693e9941fc9 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index ea36cb0fda40..c0311fbe8220 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 58ee44e2b617..9d2bd341d104 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3224,6 +3224,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 82ac8646a8d4..ab5e2eab0401 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 b552359915f1..4e418968253d 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 a15ceec1c0a0..62006165a15f 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = seqlocalam.o seqlocalxlog.o sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 8bc0e95e68c0..d82af34d538c 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -4,4 +4,5 @@ backend_sources += files(
   'seqlocalam.c',
   'seqlocalxlog.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
index e019a6f5a95d..5dec9d51ec82 100644
--- a/src/backend/access/sequence/seqlocalam.c
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -17,6 +17,7 @@
 
 #include "access/multixact.h"
 #include "access/seqlocalam.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -24,6 +25,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -230,10 +232,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;
@@ -417,7 +419,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 "heap";
@@ -432,7 +434,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];
@@ -499,7 +501,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;
@@ -547,7 +549,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;
@@ -600,7 +602,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)
 {
 	Buffer		buf;
@@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called)
  *
  * Persistence change for the local sequence Relation.
  */
-void
+static void
 seq_local_change_persistence(Relation rel, char newrelpersistence)
 {
 	Buffer		buf;
@@ -632,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 8b5303553702..f30317d1feae 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..dd1a60d827a4
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2024, 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/xact.h"
+#include "access/sequenceam.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 fd6537567ea2..e774dfa86e4e 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 58ed9d216cc0..e44633d13b60 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 41cfa4f1b9a5..e2ebede15116 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -152,6 +152,7 @@ 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;
@@ -173,7 +174,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)
@@ -241,7 +242,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 */
@@ -297,7 +298,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
 
 	/* Read sequence data */
-	seq_local_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -310,7 +311,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 */
@@ -353,7 +354,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);
 }
@@ -470,8 +471,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;
@@ -625,7 +626,7 @@ do_setval(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);
 }
@@ -1353,7 +1354,7 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		bool		is_called;
 		int64		last_value;
 
-		seq_local_get_state(seqrel, &last_value, &is_called);
+		sequence_get_state(seqrel, &last_value, &is_called);
 
 		values[0] = Int64GetDatum(last_value);
 		values[1] = BoolGetDatum(is_called);
@@ -1400,7 +1401,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		seq_local_get_state(seqrel, &result, &is_called);
+		sequence_get_state(seqrel, &result, &is_called);
 	}
 
 	sequence_close(seqrel, NoLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b1cbb14c3d08..c61dd48f2a18 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"
@@ -1025,14 +1026,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)
 	{
@@ -1045,6 +1050,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 9ecddb142314..0c79454f641e 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 db43034b9db5..21188cef3ad8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -381,6 +381,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
@@ -4981,23 +4982,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;
@@ -5034,6 +5038,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); }
 		;
@@ -6037,6 +6046,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 afcf54169c3b..a8fb08752f91 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"
@@ -518,6 +519,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 317a1f2b282f..68f160dda7d3 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 6fe268a8eec1..f3fd8d26b185 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);
 
@@ -1831,17 +1835,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.
@@ -1872,6 +1868,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
  *
@@ -3711,14 +3750,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
@@ -4343,13 +4385,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);
@@ -6425,8 +6475,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);
@@ -6438,6 +6490,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_tables.c b/src/backend/utils/misc/guc_tables.c
index d14b1678e7fe..f225c85aa196 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"
@@ -4341,6 +4342,17 @@ struct config_string ConfigureNamesString[] =
 		check_default_table_access_method, NULL, NULL
 	},
 
+	{
+		{"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets the default sequence access method for new sequences."),
+			NULL,
+			GUC_IS_NAME
+		},
+		&default_sequence_access_method,
+		DEFAULT_SEQUENCE_ACCESS_METHOD,
+		check_default_sequence_access_method, NULL, NULL
+	},
+
 	{
 		{"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default tablespace to create tables and indexes in."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index a9d8293474af..af0c0886adcc 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -750,6 +750,7 @@ autovacuum_worker_slots = 16	# autovacuum worker slots to allocate
 					#   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 7a06af48842d..d6d2a5940862 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 8b10f2313f39..8ac13b26fe6a 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2596,7 +2596,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");
@@ -3663,7 +3663,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 20bf9ea9cdf7..20b3d8520315 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1943,6 +1943,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 a79325e8a2f7..19ade0eb7e62 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5129,31 +5129,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
@@ -5178,32 +5180,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 943e56506bf1..f36e267c4b98 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 2fb3a8528781..e60eeab33c02 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 df795759bb4c..dbaceb9fd2c8 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 e6f2e93b2d6f..4bfb6434fe6f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2691,6 +2691,7 @@ SeqScanState
 SeqTable
 SeqTableData
 SeqType
+SequenceAmRoutine
 SequenceItem
 SerCommitSeqNo
 SerialControl
@@ -3764,6 +3765,7 @@ list_sort_comparator
 loc_chunk
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 local_ts_iter
 local_ts_radix_tree
@@ -4051,7 +4053,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
@@ -4278,6 +4279,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
@@ -4289,7 +4291,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.50.0

v17-0004-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From ea5501a02d4b5900a52662b1def64a66f34939a1 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 15 Aug 2025 14:35:50 +0900
Subject: [PATCH v17 4/6] 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 3c3acbaccdb5..cf7a90ee3db2 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;
@@ -2420,6 +2422,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 */
 
@@ -2687,6 +2690,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);
@@ -2791,6 +2795,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);
 
@@ -3500,6 +3507,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;
 
@@ -3662,6 +3672,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.
  */
@@ -3871,6 +3932,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);
 
@@ -4391,6 +4453,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);
@@ -5130,6 +5194,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -5189,6 +5254,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 fc7a66391633..b919641b9080 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},
@@ -1232,6 +1234,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;
@@ -1354,6 +1357,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 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"));
@@ -14341,6 +14345,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;
@@ -18824,26 +18831,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);
@@ -18863,6 +18884,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);
@@ -18934,6 +18959,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);
 	}
@@ -19056,6 +19082,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 bb451c1bae14..2d52d96514d6 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},
@@ -450,6 +452,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)
@@ -731,6 +735,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 c9776306c5c4..b2e25da92b92 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},
@@ -435,6 +437,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;
@@ -566,6 +569,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 e7a2d64f7413..e9f719460400 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -650,6 +650,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_table_access_method => {
 		dump_cmd => [
 			'pg_dump', '--no-sync',
@@ -873,6 +882,7 @@ my %full_runs = (
 	no_policies => 1,
 	no_privs => 1,
 	no_statistics => 1,
+	no_sequence_access_method => 1,
 	no_table_access_method => 1,
 	pg_dumpall_dbprivs => 1,
 	pg_dumpall_exclude => 1,
@@ -4911,6 +4921,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
@@ -4939,6 +4961,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 fd4ecf01a0a0..895e324ba193 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 9f639f61db02..94a5bad771b2 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.50.0

v17-0005-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From 4afc344e06d89e1d9dbdddfcf84e8597d0a80539 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v17 5/6] 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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 20ccb2d6b544..4a892d946e64 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9761,6 +9761,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 af476c82fcc1..9f369cc89930 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..3067dc4d4df0 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>SEQUENCE</literal> and
+      <literal>INDEX</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 1e283f13d15c..52c6096e4ba2 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..a96170bfac03
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.50.0

v17-0006-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From 2bfa51d6922599995cde943b1547a4190668f094 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 26 Apr 2024 15:09:00 +0900
Subject: [PATCH v17 6/6] snowflake: Add sequence AM based on it

This includes documentation and a basic implementation, though this is
in a very early stage.
---
 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            | 570 +++++++++++++++++++++++
 contrib/snowflake/snowflake.control      |   5 +
 contrib/snowflake/sql/snowflake.sql      |  29 ++
 13 files changed, 858 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 ed30ee7d639f..e3bd2b464172 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..567669eea790
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2024, 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 += bloom
+
+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..be4c4039ecd0
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,570 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2024, 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/sequenceam.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 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(snowflake_magic));
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+	sm->magic = SNOWFLAKE_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 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, (Item) 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)
+{
+	Page		page;
+	ItemId		lp;
+	snowflake_magic *sm;
+	Form_snowflake_data	seq;
+
+	*buf = ReadBuffer(rel, 0);
+	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(*buf);
+	sm = (snowflake_magic *) PageGetSpecialPointer(page);
+
+	if (sm->magic != SNOWFLAKE_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_snowflake_data) GETSTRUCT(seqdatatuple);
+
+	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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_snowflake_data seq;
+
+	seq = read_snowflake(rel, &buf, &seqdatatuple);
+	*last_value = seq->count;
+	*is_called = seq->is_called;
+	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.50.0

#39Kirill Reshke
reshkekirill@gmail.com
In reply to: Michael Paquier (#38)
Re: Sequence Access Methods, round two

On Mon, 18 Aug 2025 at 09:49, Michael Paquier <michael@paquier.xyz> wrote:

We could just
reuse AlterColumn, saving from the extra sub-command. What do you
think?

Yes, this resonates with me better.

Rebased the rest as attached, for now.

I have a small enhancement to the patch set.
In v17-0003:
```
reshke@yezzey-cbdb-bench:~/cpg$ git diff
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 8ac13b26fe6..84b7f55fd73 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -3365,7 +3365,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", "TABLE", "SEQUENCE");
        /* Complete "CREATE ACCESS METHOD <name> TYPE <type>" */
        else if (Matches("CREATE", "ACCESS", "METHOD", MatchAny,
"TYPE", MatchAny))
                COMPLETE_WITH("HANDLER");
```

--
Best regards,
Kirill Reshke

#40Michael Paquier
michael@paquier.xyz
In reply to: Kirill Reshke (#39)
Re: Sequence Access Methods, round two

On Mon, Aug 18, 2025 at 06:15:05PM +0500, Kirill Reshke wrote:

Yes, this resonates with me better.

After sleeping on it, yes, perhaps that's the right move.
test_ddl_deparse reports the attributes as related to a sequence, but
it's really how this is handled internally anyway with the internal
call of DefineRelation(). This entirely decouples the relation
creation and the creation of its attributes.

@@ -3365,7 +3365,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", "TABLE", "SEQUENCE");
/* Complete "CREATE ACCESS METHOD <name> TYPE <type>" */
else if (Matches("CREATE", "ACCESS", "METHOD", MatchAny,
"TYPE", MatchAny))
COMPLETE_WITH("HANDLER");

Right, thanks.
--
Michael

#41Kirill Reshke
reshkekirill@gmail.com
In reply to: Kirill Reshke (#39)
1 attachment(s)
Re: Sequence Access Methods, round two

On Mon, 18 Aug 2025 at 18:15, I wrote:

I have a small enhancement to the patch set.

I played with patch sets some more, trying to break things in nasty
ways (something like CREATE OPERATOR FAMILY ff USING seqlocal or other
incorrect ddl).
But I had no success. I will try some more later, maybe I will find something

But while doing so I noticed another small enhancement for the patch
set. This change is not that big, so attaching as diff.

PFA diff which describes the access method of sequence on \d+ psql
meta-command likewise we do it for tables.

Example:
```
reshke=# \d+ dd
Sequence "public.dd"
Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
--------+-------+---------+---------------------+-----------+---------+-------
bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
Access method: seqlocal

reshke=# \d+ s4
Sequence "public.s4"
Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
--------+-------+---------+---------------------+-----------+---------+-------
bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
Access method: aa
```

--
Best regards,
Kirill Reshke

Attachments:

describe_enhasement.diffapplication/octet-stream; name=describe_enhasement.diffDownload
reshke@yezzey-cbdb-bench:~/cpg/src/bin/psql$ git diff describe.c
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index d6d2a594086..7840e59e40e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1761,7 +1761,8 @@ describeOneTableDetails(const char *schemaname,
        {
                PGresult   *result = NULL;
                printQueryOpt myopt = pset.popt;
-               char       *footers[2] = {NULL, NULL};
+               char       *footers[3] = {NULL, NULL, NULL};
+               int footer_cnt                  = 0;

                if (pset.sversion >= 100000)
                {
@@ -1846,11 +1847,11 @@ describeOneTableDetails(const char *schemaname,
                        switch (PQgetvalue(result, 0, 1)[0])
                        {
                                case 'a':
-                                       footers[0] = psprintf(_("Owned by: %s"),
+                                       footers[footer_cnt++] = psprintf(_("Owned by: %s"),
                                                                                  PQgetvalue(result, 0, 0));
                                        break;
                                case 'i':
-                                       footers[0] = psprintf(_("Sequence for identity column: %s"),
+                                       footers[footer_cnt++] = psprintf(_("Sequence for identity column: %s"),
                                                                                  PQgetvalue(result, 0, 0));
                                        break;
                        }
@@ -1864,6 +1865,10 @@ describeOneTableDetails(const char *schemaname,
                        printfPQExpBuffer(&title, _("Sequence \"%s.%s\""),
                                                          schemaname, relationname);

+               /* Access method info */
+               if (verbose && tableinfo.relam != NULL && !pset.hide_tableam)
+                       footers[footer_cnt++] = psprintf(_("Access method: %s"), tableinfo.relam);
+
                myopt.footers = footers;
                myopt.topt.default_footer = false;
                myopt.title = title.data;
@@ -1872,6 +1877,7 @@ describeOneTableDetails(const char *schemaname,
                printQuery(res, &myopt, pset.queryFout, false, pset.logfile);

                free(footers[0]);
+               free(footers[1]);

                retval = true;
                goto error_return;              /* not an error, just return early */
#42Michael Paquier
michael@paquier.xyz
In reply to: Kirill Reshke (#41)
7 attachment(s)
Re: Sequence Access Methods, round two

On Tue, Aug 19, 2025 at 10:14:33AM +0500, Kirill Reshke wrote:

I played with patch sets some more, trying to break things in nasty
ways (something like CREATE OPERATOR FAMILY ff USING seqlocal or other
incorrect ddl).
But I had no success. I will try some more later, maybe I will find something

Thanks.

PFA diff which describes the access method of sequence on \d+ psql
meta-command likewise we do it for tables.

Yes, that may be useful, but I don't think that this should use the
psql variable to hide table AMs. I am rebasing a new patch set, v18,
with a couple of changes:
- Added your feedback about psql.
- I have put more thoughts into the code shared between the in-core
sequence method and the snowflake one, and looked at reducing the
duplication between the two. At the end, I have introduced a new
header called sequence_page.h, which is able to reduce the work for
AMs when these rely on a single page through the addition of macros
able to initialize and read sequence pages. In this patch set, this
new part is labelled with 0006.
- Fixed a few more things, like comments.
--
Michael

Attachments:

v18-0001-Integrate-addition-of-attributes-for-sequences-w.patchtext/x-diff; charset=us-asciiDownload
From 897ea0253b798e5a80bd204be464605bfaef7e4f Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v18 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               | 29 +++++++++++++++++--
 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, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 86a236bd58b1..58ee44e2b617 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2412,6 +2412,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 a3c8cff97b03..9569989f052a 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -136,6 +136,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;
@@ -174,7 +177,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;
@@ -198,7 +200,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;
@@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 082a3575d621..67f47bf63523 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4645,6 +4645,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 */
@@ -4940,6 +4941,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,
@@ -5372,6 +5380,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,
@@ -6587,6 +6596,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 4f4191b0ea6b..9e5c2ec2a14e 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1658,7 +1658,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 50d0354a3417..ed31059ef584 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
@@ -70,7 +73,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 193669f2bc1e..254fdf90c79a 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.50.1

v18-0002-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From 63be7cbf7aff34f2ea57262fdcd2ee3ab0d137f3 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 09:18:40 +0900
Subject: [PATCH v18 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.
- seqlocalxlog.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/seqlocalam.h               |  58 ++
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 .../rmgrdesc/{seqdesc.c => seqlocaldesc.c}    |  20 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/meson.build       |   2 +
 src/backend/access/sequence/seqlocalam.c      | 634 ++++++++++++++++++
 src/backend/access/sequence/seqlocalxlog.c    |  82 +++
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 634 +-----------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 14 files changed, 822 insertions(+), 622 deletions(-)
 create mode 100644 src/include/access/seqlocalam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%)
 create mode 100644 src/backend/access/sequence/seqlocalam.c
 create mode 100644 src/backend/access/sequence/seqlocalxlog.c

diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 8e7fc9db8778..b942b25350bc 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/seqlocalam.h b/src/include/access/seqlocalam.h
new file mode 100644
index 000000000000..7d75823a38c4
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2025, 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 "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_SEQ_LOCAL_LOG			0x00
+
+typedef struct xl_seq_local_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_seq_local_rec;
+
+/*
+ * 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;
+
+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);
+
+/* 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);
+extern void seq_local_change_persistence(Relation rel,
+										 char newrelpersistence);
+
+#endif							/* SEQLOCALAM_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 96c98e800c22..6b3b3e1e44c1 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 0d289d77fcf7..6ffbcb2c4735 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.c
  *
  * Portions Copyright (c) 1996-2025, 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.h"
+#include "access/seqlocalam.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..a15ceec1c0a0 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 ec9ab9b7e9db..8bc0e95e68c0 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2025, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'seqlocalam.c',
+  'seqlocalxlog.c',
   'sequence.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
new file mode 100644
index 000000000000..762f2dae3255
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -0,0 +1,634 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/multixact.h"
+#include "access/seqlocalam.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"
+
+
+/*
+ * 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
+
+/* 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, (Item) 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 "heap";
+}
+
+/*
+ * 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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_seq_local_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	*last_value = seq->last_value;
+	*is_called = seq->is_called;
+
+	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/sequence/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c
new file mode 100644
index 000000000000..53f404b76920
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalxlog.c
@@ -0,0 +1,82 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalxlog.c
+ *	  WAL replay logic for 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/seqlocalxlog.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/seqlocalam.h"
+#include "access/xlogutils.h"
+#include "storage/block.h"
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+seq_local_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+seq_local_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record);
+	seq_local_magic *sm;
+
+	if (info != XLOG_SEQ_LOCAL_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = (Page) BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	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_local_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec);
+
+	if (PageAddItem(localpage, (Item) item, itemsz,
+					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "seq_local_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 1b7499726eb0..cc92268937b1 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 9569989f052a..41cfa4f1b9a5 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -50,23 +51,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -96,13 +80,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,
@@ -134,14 +114,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
@@ -174,35 +148,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;
@@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	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)
@@ -292,10 +222,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;
@@ -306,7 +232,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))
@@ -315,40 +240,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 */
@@ -357,106 +250,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, (Item) 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
  *
@@ -468,10 +261,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;
@@ -480,7 +270,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	bool		reset_state = false;
 	bool		is_called;
 	int64		last_value;
-	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
 	relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -507,16 +296,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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -526,32 +307,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 */
@@ -580,8 +339,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
@@ -596,10 +353,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);
 }
@@ -662,24 +416,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);
@@ -724,105 +469,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;
@@ -832,69 +481,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;
@@ -984,9 +570,6 @@ do_setval(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,
@@ -1020,9 +603,6 @@ do_setval(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),
@@ -1044,37 +624,8 @@ do_setval(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);
 }
@@ -1215,62 +766,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
@@ -1855,16 +1350,13 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		Buffer		buf;
-		HeapTupleData seqtuple;
-		Form_pg_sequence_data seq;
+		bool		is_called;
+		int64		last_value;
 
-		seq = read_seq_tuple(seqrel, &buf, &seqtuple);
+		seq_local_get_state(seqrel, &last_value, &is_called);
 
-		values[0] = Int64GetDatum(seq->last_value);
-		values[1] = BoolGetDatum(seq->is_called);
-
-		UnlockReleaseBuffer(buf);
+		values[0] = Int64GetDatum(last_value);
+		values[1] = BoolGetDatum(is_called);
 	}
 	else
 		memset(isnull, true, sizeof(isnull));
@@ -1908,17 +1400,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);
 	}
+
 	sequence_close(seqrel, NoLock);
 
 	if (is_called)
@@ -1927,57 +1411,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
-
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = (Page) BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, (Item) item, itemsz,
-					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1992,14 +1425,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c767e..8d1195de2637 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/seqlocaldesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index fac509ed134e..2fcf9fc4392a 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index f26d75e01cfd..2495a28d26a1 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.50.1

v18-0003-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From 4b78270ad51a6db35a480751cc3d3ef3af446b4c Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 11:40:10 +0900
Subject: [PATCH v18 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               |  15 --
 src/include/access/sequenceam.h               | 181 ++++++++++++++++++
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/seqlocalam.c      |  41 +++-
 src/backend/access/sequence/sequence.c        |   3 +-
 src/backend/access/sequence/sequenceamapi.c   | 145 ++++++++++++++
 src/backend/catalog/heap.c                    |   6 +-
 src/backend/commands/amcmds.c                 |  16 ++
 src/backend/commands/sequence.c               |  23 +--
 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_tables.c           |  12 ++
 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 +-
 40 files changed, 696 insertions(+), 183 deletions(-)
 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
index 7d75823a38c4..5a0d77e392f4 100644
--- a/src/include/access/seqlocalam.h
+++ b/src/include/access/seqlocalam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_SEQ_LOCAL_LOG			0x00
@@ -41,18 +40,4 @@ 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);
 
-/* 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);
-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..a38e1d478a60
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,181 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2025, 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.
+	 */
+	void		(*get_state) (Relation rel, int64 *last_value, bool *is_called);
+
+	/*
+	 * 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)
+{
+	rel->rd_sequenceam->get_state(rel, last_value, is_called);
+}
+
+/*
+ * 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 26d15928a155..8f076fcec958 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 6e98a0930c27..080bea5031a9 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 07d182da796a..012a2863c037 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 118d6da1ace0..b272b4885e6e 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',
@@ -7894,6 +7900,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 cb730aeac864..d0054c5c988c 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 dd22b5efdfd9..6790728aced3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -145,6 +145,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 9ac0b67683d3..7693e9941fc9 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index ea36cb0fda40..c0311fbe8220 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 58ee44e2b617..9d2bd341d104 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3224,6 +3224,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 82ac8646a8d4..ab5e2eab0401 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 b552359915f1..4e418968253d 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 a15ceec1c0a0..62006165a15f 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = seqlocalam.o seqlocalxlog.o sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 8bc0e95e68c0..d82af34d538c 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -4,4 +4,5 @@ backend_sources += files(
   'seqlocalam.c',
   'seqlocalxlog.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
index 762f2dae3255..954749cd8fb6 100644
--- a/src/backend/access/sequence/seqlocalam.c
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -17,6 +17,7 @@
 
 #include "access/multixact.h"
 #include "access/seqlocalam.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -24,6 +25,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -230,10 +232,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;
@@ -417,7 +419,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 "heap";
@@ -432,7 +434,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];
@@ -499,7 +501,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;
@@ -547,7 +549,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;
@@ -600,7 +602,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)
 {
 	Buffer		buf;
@@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called)
  *
  * Persistence change for the local sequence Relation.
  */
-void
+static void
 seq_local_change_persistence(Relation rel, char newrelpersistence)
 {
 	Buffer		buf;
@@ -632,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 8b5303553702..f30317d1feae 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..a77f6da07cb1
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2025, 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/xact.h"
+#include "access/sequenceam.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 fd6537567ea2..e774dfa86e4e 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 58ed9d216cc0..e44633d13b60 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 41cfa4f1b9a5..e2ebede15116 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -152,6 +152,7 @@ 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;
@@ -173,7 +174,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)
@@ -241,7 +242,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 */
@@ -297,7 +298,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
 
 	/* Read sequence data */
-	seq_local_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -310,7 +311,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 */
@@ -353,7 +354,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);
 }
@@ -470,8 +471,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;
@@ -625,7 +626,7 @@ do_setval(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);
 }
@@ -1353,7 +1354,7 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		bool		is_called;
 		int64		last_value;
 
-		seq_local_get_state(seqrel, &last_value, &is_called);
+		sequence_get_state(seqrel, &last_value, &is_called);
 
 		values[0] = Int64GetDatum(last_value);
 		values[1] = BoolGetDatum(is_called);
@@ -1400,7 +1401,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		seq_local_get_state(seqrel, &result, &is_called);
+		sequence_get_state(seqrel, &result, &is_called);
 	}
 
 	sequence_close(seqrel, NoLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 67f47bf63523..cb12351889bf 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"
@@ -1025,14 +1026,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)
 	{
@@ -1045,6 +1050,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 9ecddb142314..0c79454f641e 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 db43034b9db5..21188cef3ad8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -381,6 +381,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
@@ -4981,23 +4982,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;
@@ -5034,6 +5038,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); }
 		;
@@ -6037,6 +6046,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 afcf54169c3b..a8fb08752f91 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"
@@ -518,6 +519,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 317a1f2b282f..68f160dda7d3 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 6fe268a8eec1..f3fd8d26b185 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);
 
@@ -1831,17 +1835,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.
@@ -1872,6 +1868,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
  *
@@ -3711,14 +3750,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
@@ -4343,13 +4385,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);
@@ -6425,8 +6475,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);
@@ -6438,6 +6490,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_tables.c b/src/backend/utils/misc/guc_tables.c
index d14b1678e7fe..f225c85aa196 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"
@@ -4341,6 +4342,17 @@ struct config_string ConfigureNamesString[] =
 		check_default_table_access_method, NULL, NULL
 	},
 
+	{
+		{"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets the default sequence access method for new sequences."),
+			NULL,
+			GUC_IS_NAME
+		},
+		&default_sequence_access_method,
+		DEFAULT_SEQUENCE_ACCESS_METHOD,
+		check_default_sequence_access_method, NULL, NULL
+	},
+
 	{
 		{"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default tablespace to create tables and indexes in."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index a9d8293474af..af0c0886adcc 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -750,6 +750,7 @@ autovacuum_worker_slots = 16	# autovacuum worker slots to allocate
 					#   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 7a06af48842d..d6d2a5940862 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 8b10f2313f39..1e2d2eb6b609 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2596,7 +2596,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");
@@ -3365,7 +3365,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");
@@ -3663,7 +3663,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 20bf9ea9cdf7..20b3d8520315 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1943,6 +1943,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 a79325e8a2f7..19ade0eb7e62 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5129,31 +5129,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
@@ -5178,32 +5180,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 943e56506bf1..f36e267c4b98 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 2fb3a8528781..e60eeab33c02 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 df795759bb4c..dbaceb9fd2c8 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 e4a9ec65ab4d..25c3674ec11f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2691,6 +2691,7 @@ SeqScanState
 SeqTable
 SeqTableData
 SeqType
+SequenceAmRoutine
 SequenceItem
 SerCommitSeqNo
 SerialControl
@@ -3762,6 +3763,7 @@ list_sort_comparator
 loc_chunk
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 local_ts_iter
 local_ts_radix_tree
@@ -4049,7 +4051,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
@@ -4276,6 +4277,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
@@ -4287,7 +4289,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.50.1

v18-0004-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From 809b494d8f21df7e58c800f9047d34f5f9bb4607 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 15 Aug 2025 14:35:50 +0900
Subject: [PATCH v18 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 3c3acbaccdb5..cf7a90ee3db2 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;
@@ -2420,6 +2422,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 */
 
@@ -2687,6 +2690,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);
@@ -2791,6 +2795,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);
 
@@ -3500,6 +3507,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;
 
@@ -3662,6 +3672,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.
  */
@@ -3871,6 +3932,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);
 
@@ -4391,6 +4453,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);
@@ -5130,6 +5194,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -5189,6 +5254,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 fc7a66391633..b919641b9080 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},
@@ -1232,6 +1234,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;
@@ -1354,6 +1357,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 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"));
@@ -14341,6 +14345,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;
@@ -18824,26 +18831,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);
@@ -18863,6 +18884,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);
@@ -18934,6 +18959,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);
 	}
@@ -19056,6 +19082,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 bb451c1bae14..2d52d96514d6 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},
@@ -450,6 +452,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)
@@ -731,6 +735,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 c9776306c5c4..b2e25da92b92 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},
@@ -435,6 +437,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;
@@ -566,6 +569,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 e7a2d64f7413..e9f719460400 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -650,6 +650,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_table_access_method => {
 		dump_cmd => [
 			'pg_dump', '--no-sync',
@@ -873,6 +882,7 @@ my %full_runs = (
 	no_policies => 1,
 	no_privs => 1,
 	no_statistics => 1,
+	no_sequence_access_method => 1,
 	no_table_access_method => 1,
 	pg_dumpall_dbprivs => 1,
 	pg_dumpall_exclude => 1,
@@ -4911,6 +4921,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
@@ -4939,6 +4961,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 fd4ecf01a0a0..895e324ba193 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 9f639f61db02..94a5bad771b2 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.50.1

v18-0005-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From 91117362557772cb52cd792312c734a8f6b49ecf Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v18 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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 20ccb2d6b544..4a892d946e64 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9761,6 +9761,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 af476c82fcc1..9f369cc89930 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..3067dc4d4df0 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>SEQUENCE</literal> and
+      <literal>INDEX</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 1e283f13d15c..52c6096e4ba2 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..a96170bfac03
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.50.1

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

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

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

v18-0007-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From dd68d10650cbf7877d837a314539a0c1246c5a3e Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 11:32:05 +0900
Subject: [PATCH v18 7/7] snowflake: Add sequence AM based on it

This includes documentation and a basic implementation, though this is
in a very early stage.
---
 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            | 510 +++++++++++++++++++++++
 contrib/snowflake/snowflake.control      |   5 +
 contrib/snowflake/sql/snowflake.sql      |  29 ++
 13 files changed, 798 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 ed30ee7d639f..e3bd2b464172 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..bb0270529852
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2025, 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 += bloom
+
+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..6acee691fafc
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,510 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2025, 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/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, (Item) 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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_snowflake_data seq;
+
+	seq = read_snowflake(rel, &buf, &seqdatatuple);
+	*last_value = seq->count;
+	*is_called = seq->is_called;
+	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.50.1

#43Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#42)
7 attachment(s)
Re: Sequence Access Methods, round two

On Thu, Aug 21, 2025 at 11:57:58AM +0900, Michael Paquier wrote:

Yes, that may be useful, but I don't think that this should use the
psql variable to hide table AMs. I am rebasing a new patch set, v18,
with a couple of changes:
- Added your feedback about psql.
- I have put more thoughts into the code shared between the in-core
sequence method and the snowflake one, and looked at reducing the
duplication between the two. At the end, I have introduced a new
header called sequence_page.h, which is able to reduce the work for
AMs when these rely on a single page through the addition of macros
able to initialize and read sequence pages. In this patch set, this
new part is labelled with 0006.
- Fixed a few more things, like comments.

Another rebase required due to the business with BufferGetPage() in
710e6c4301ee.

By the way, I have remembered the reason why I have introduced
AT_AddColumnToSequence as a new ALTER TABLE subcommands: ACL check for
sequences. Like views, this simplifies the object type check a lot.
--
Michael

Attachments:

v19-0001-Integrate-addition-of-attributes-for-sequences-w.patchtext/x-diff; charset=us-asciiDownload
From d9339d6b867c1f5fcdbac3e56b93638cb8997c5e Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v19 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               | 29 +++++++++++++++++--
 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, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 86a236bd58b1..58ee44e2b617 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2412,6 +2412,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 636d3c3ec737..0befbb2b5e12 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -136,6 +136,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;
@@ -174,7 +177,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;
@@ -198,7 +200,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;
@@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 082a3575d621..67f47bf63523 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4645,6 +4645,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 */
@@ -4940,6 +4941,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,
@@ -5372,6 +5380,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,
@@ -6587,6 +6596,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 5f442bc3bd4e..305847655787 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1658,7 +1658,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 50d0354a3417..ed31059ef584 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
@@ -70,7 +73,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 193669f2bc1e..254fdf90c79a 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

v19-0002-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From 36d6d4699ab74a8e3a069994c2b96a11434e9f7d Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 09:18:40 +0900
Subject: [PATCH v19 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.
- seqlocalxlog.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/seqlocalam.h               |  58 ++
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 .../rmgrdesc/{seqdesc.c => seqlocaldesc.c}    |  20 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/meson.build       |   2 +
 src/backend/access/sequence/seqlocalam.c      | 634 ++++++++++++++++++
 src/backend/access/sequence/seqlocalxlog.c    |  82 +++
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 634 +-----------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 14 files changed, 822 insertions(+), 622 deletions(-)
 create mode 100644 src/include/access/seqlocalam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%)
 create mode 100644 src/backend/access/sequence/seqlocalam.c
 create mode 100644 src/backend/access/sequence/seqlocalxlog.c

diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 8e7fc9db8778..b942b25350bc 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/seqlocalam.h b/src/include/access/seqlocalam.h
new file mode 100644
index 000000000000..7d75823a38c4
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2025, 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 "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_SEQ_LOCAL_LOG			0x00
+
+typedef struct xl_seq_local_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_seq_local_rec;
+
+/*
+ * 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;
+
+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);
+
+/* 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);
+extern void seq_local_change_persistence(Relation rel,
+										 char newrelpersistence);
+
+#endif							/* SEQLOCALAM_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 96c98e800c22..6b3b3e1e44c1 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 0d289d77fcf7..6ffbcb2c4735 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.c
  *
  * Portions Copyright (c) 1996-2025, 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.h"
+#include "access/seqlocalam.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..a15ceec1c0a0 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 ec9ab9b7e9db..8bc0e95e68c0 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2025, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'seqlocalam.c',
+  'seqlocalxlog.c',
   'sequence.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
new file mode 100644
index 000000000000..762f2dae3255
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -0,0 +1,634 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/multixact.h"
+#include "access/seqlocalam.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"
+
+
+/*
+ * 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
+
+/* 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, (Item) 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 "heap";
+}
+
+/*
+ * 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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_seq_local_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	*last_value = seq->last_value;
+	*is_called = seq->is_called;
+
+	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/sequence/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c
new file mode 100644
index 000000000000..2b8424514a51
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalxlog.c
@@ -0,0 +1,82 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalxlog.c
+ *	  WAL replay logic for 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/seqlocalxlog.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/seqlocalam.h"
+#include "access/xlogutils.h"
+#include "storage/block.h"
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+seq_local_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+seq_local_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record);
+	seq_local_magic *sm;
+
+	if (info != XLOG_SEQ_LOCAL_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	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_local_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec);
+
+	if (PageAddItem(localpage, (Item) item, itemsz,
+					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "seq_local_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 1b7499726eb0..cc92268937b1 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0befbb2b5e12..41cfa4f1b9a5 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -50,23 +51,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -96,13 +80,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,
@@ -134,14 +114,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
@@ -174,35 +148,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;
@@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	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)
@@ -292,10 +222,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;
@@ -306,7 +232,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))
@@ -315,40 +240,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 */
@@ -357,106 +250,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, (Item) 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
  *
@@ -468,10 +261,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;
@@ -480,7 +270,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	bool		reset_state = false;
 	bool		is_called;
 	int64		last_value;
-	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
 	relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -507,16 +296,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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -526,32 +307,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 */
@@ -580,8 +339,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
@@ -596,10 +353,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);
 }
@@ -662,24 +416,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);
@@ -724,105 +469,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;
@@ -832,69 +481,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;
@@ -984,9 +570,6 @@ do_setval(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,
@@ -1020,9 +603,6 @@ do_setval(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),
@@ -1044,37 +624,8 @@ do_setval(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);
 }
@@ -1215,62 +766,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
@@ -1855,16 +1350,13 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		Buffer		buf;
-		HeapTupleData seqtuple;
-		Form_pg_sequence_data seq;
+		bool		is_called;
+		int64		last_value;
 
-		seq = read_seq_tuple(seqrel, &buf, &seqtuple);
+		seq_local_get_state(seqrel, &last_value, &is_called);
 
-		values[0] = Int64GetDatum(seq->last_value);
-		values[1] = BoolGetDatum(seq->is_called);
-
-		UnlockReleaseBuffer(buf);
+		values[0] = Int64GetDatum(last_value);
+		values[1] = BoolGetDatum(is_called);
 	}
 	else
 		memset(isnull, true, sizeof(isnull));
@@ -1908,17 +1400,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);
 	}
+
 	sequence_close(seqrel, NoLock);
 
 	if (is_called)
@@ -1927,57 +1411,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
-
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, (Item) item, itemsz,
-					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1992,14 +1425,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c767e..8d1195de2637 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/seqlocaldesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index fac509ed134e..2fcf9fc4392a 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index f26d75e01cfd..2495a28d26a1 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

v19-0003-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From eafc0a156b8fcf926a36df8aaab4d6577b02dc03 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 11:40:10 +0900
Subject: [PATCH v19 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               |  15 --
 src/include/access/sequenceam.h               | 181 ++++++++++++++++++
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/seqlocalam.c      |  41 +++-
 src/backend/access/sequence/sequence.c        |   3 +-
 src/backend/access/sequence/sequenceamapi.c   | 145 ++++++++++++++
 src/backend/catalog/heap.c                    |   6 +-
 src/backend/commands/amcmds.c                 |  16 ++
 src/backend/commands/sequence.c               |  23 +--
 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_tables.c           |  12 ++
 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 +-
 40 files changed, 696 insertions(+), 183 deletions(-)
 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
index 7d75823a38c4..5a0d77e392f4 100644
--- a/src/include/access/seqlocalam.h
+++ b/src/include/access/seqlocalam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_SEQ_LOCAL_LOG			0x00
@@ -41,18 +40,4 @@ 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);
 
-/* 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);
-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..a38e1d478a60
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,181 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2025, 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.
+	 */
+	void		(*get_state) (Relation rel, int64 *last_value, bool *is_called);
+
+	/*
+	 * 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)
+{
+	rel->rd_sequenceam->get_state(rel, last_value, is_called);
+}
+
+/*
+ * 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 26d15928a155..8f076fcec958 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 6e98a0930c27..080bea5031a9 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 07d182da796a..012a2863c037 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 118d6da1ace0..b272b4885e6e 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',
@@ -7894,6 +7900,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 cb730aeac864..d0054c5c988c 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 dd22b5efdfd9..6790728aced3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -145,6 +145,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 9ac0b67683d3..7693e9941fc9 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index ea36cb0fda40..c0311fbe8220 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 58ee44e2b617..9d2bd341d104 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3224,6 +3224,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 82ac8646a8d4..ab5e2eab0401 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 b552359915f1..4e418968253d 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 a15ceec1c0a0..62006165a15f 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = seqlocalam.o seqlocalxlog.o sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 8bc0e95e68c0..d82af34d538c 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -4,4 +4,5 @@ backend_sources += files(
   'seqlocalam.c',
   'seqlocalxlog.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
index 762f2dae3255..954749cd8fb6 100644
--- a/src/backend/access/sequence/seqlocalam.c
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -17,6 +17,7 @@
 
 #include "access/multixact.h"
 #include "access/seqlocalam.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -24,6 +25,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -230,10 +232,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;
@@ -417,7 +419,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 "heap";
@@ -432,7 +434,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];
@@ -499,7 +501,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;
@@ -547,7 +549,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;
@@ -600,7 +602,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)
 {
 	Buffer		buf;
@@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called)
  *
  * Persistence change for the local sequence Relation.
  */
-void
+static void
 seq_local_change_persistence(Relation rel, char newrelpersistence)
 {
 	Buffer		buf;
@@ -632,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 8b5303553702..f30317d1feae 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..a77f6da07cb1
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2025, 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/xact.h"
+#include "access/sequenceam.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 fd6537567ea2..e774dfa86e4e 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 58ed9d216cc0..e44633d13b60 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 41cfa4f1b9a5..e2ebede15116 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -152,6 +152,7 @@ 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;
@@ -173,7 +174,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)
@@ -241,7 +242,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 */
@@ -297,7 +298,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
 
 	/* Read sequence data */
-	seq_local_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -310,7 +311,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 */
@@ -353,7 +354,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);
 }
@@ -470,8 +471,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;
@@ -625,7 +626,7 @@ do_setval(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);
 }
@@ -1353,7 +1354,7 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		bool		is_called;
 		int64		last_value;
 
-		seq_local_get_state(seqrel, &last_value, &is_called);
+		sequence_get_state(seqrel, &last_value, &is_called);
 
 		values[0] = Int64GetDatum(last_value);
 		values[1] = BoolGetDatum(is_called);
@@ -1400,7 +1401,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		seq_local_get_state(seqrel, &result, &is_called);
+		sequence_get_state(seqrel, &result, &is_called);
 	}
 
 	sequence_close(seqrel, NoLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 67f47bf63523..cb12351889bf 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"
@@ -1025,14 +1026,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)
 	{
@@ -1045,6 +1050,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 9ecddb142314..0c79454f641e 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 db43034b9db5..21188cef3ad8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -381,6 +381,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
@@ -4981,23 +4982,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;
@@ -5034,6 +5038,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); }
 		;
@@ -6037,6 +6046,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 afcf54169c3b..a8fb08752f91 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"
@@ -518,6 +519,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 317a1f2b282f..68f160dda7d3 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 6fe268a8eec1..f3fd8d26b185 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);
 
@@ -1831,17 +1835,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.
@@ -1872,6 +1868,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
  *
@@ -3711,14 +3750,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
@@ -4343,13 +4385,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);
@@ -6425,8 +6475,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);
@@ -6438,6 +6490,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_tables.c b/src/backend/utils/misc/guc_tables.c
index f137129209f6..df446c46df9e 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"
@@ -4414,6 +4415,17 @@ struct config_string ConfigureNamesString[] =
 		check_default_table_access_method, NULL, NULL
 	},
 
+	{
+		{"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets the default sequence access method for new sequences."),
+			NULL,
+			GUC_IS_NAME
+		},
+		&default_sequence_access_method,
+		DEFAULT_SEQUENCE_ACCESS_METHOD,
+		check_default_sequence_access_method, NULL, NULL
+	},
+
 	{
 		{"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default tablespace to create tables and indexes in."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index a9d8293474af..af0c0886adcc 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -750,6 +750,7 @@ autovacuum_worker_slots = 16	# autovacuum worker slots to allocate
 					#   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 7a06af48842d..d6d2a5940862 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 8b10f2313f39..1e2d2eb6b609 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2596,7 +2596,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");
@@ -3365,7 +3365,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");
@@ -3663,7 +3663,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 20bf9ea9cdf7..20b3d8520315 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1943,6 +1943,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 a79325e8a2f7..19ade0eb7e62 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5129,31 +5129,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
@@ -5178,32 +5180,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 943e56506bf1..f36e267c4b98 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 2fb3a8528781..e60eeab33c02 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 df795759bb4c..dbaceb9fd2c8 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 a13e81628902..56b9e02aae36 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2692,6 +2692,7 @@ SeqScanState
 SeqTable
 SeqTableData
 SeqType
+SequenceAmRoutine
 SequenceItem
 SerCommitSeqNo
 SerialControl
@@ -3763,6 +3764,7 @@ list_sort_comparator
 loc_chunk
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 local_ts_iter
 local_ts_radix_tree
@@ -4050,7 +4052,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
@@ -4277,6 +4278,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
@@ -4288,7 +4290,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

v19-0004-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From ca8ebaa4d9d0211dcb0f09ae95af84ffa4d390d7 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 15 Aug 2025 14:35:50 +0900
Subject: [PATCH v19 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 058b5d659bac..77ab1958d8f6 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;
@@ -2420,6 +2422,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 */
 
@@ -2687,6 +2690,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);
@@ -2791,6 +2795,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);
 
@@ -3500,6 +3507,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;
 
@@ -3662,6 +3672,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.
  */
@@ -3871,6 +3932,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);
 
@@ -4391,6 +4453,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);
@@ -5130,6 +5194,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -5189,6 +5254,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 fc7a66391633..b919641b9080 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},
@@ -1232,6 +1234,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;
@@ -1354,6 +1357,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 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"));
@@ -14341,6 +14345,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;
@@ -18824,26 +18831,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);
@@ -18863,6 +18884,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);
@@ -18934,6 +18959,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);
 	}
@@ -19056,6 +19082,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 bb451c1bae14..2d52d96514d6 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},
@@ -450,6 +452,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)
@@ -731,6 +735,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 c9776306c5c4..b2e25da92b92 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},
@@ -435,6 +437,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;
@@ -566,6 +569,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 e7a2d64f7413..e9f719460400 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -650,6 +650,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_table_access_method => {
 		dump_cmd => [
 			'pg_dump', '--no-sync',
@@ -873,6 +882,7 @@ my %full_runs = (
 	no_policies => 1,
 	no_privs => 1,
 	no_statistics => 1,
+	no_sequence_access_method => 1,
 	no_table_access_method => 1,
 	pg_dumpall_dbprivs => 1,
 	pg_dumpall_exclude => 1,
@@ -4911,6 +4921,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
@@ -4939,6 +4961,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 fd4ecf01a0a0..895e324ba193 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 9f639f61db02..94a5bad771b2 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

v19-0005-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From 8ebee1a7313f688edf45775b2c390250f9b95628 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v19 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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 0a4b3e55ba5e..19b0c94b327d 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9762,6 +9762,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 af476c82fcc1..9f369cc89930 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..3067dc4d4df0 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>SEQUENCE</literal> and
+      <literal>INDEX</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 1e283f13d15c..52c6096e4ba2 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..a96170bfac03
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.51.0

v19-0006-Refactor-logic-for-page-manipulations-of-sequenc.patchtext/x-diff; charset=us-asciiDownload
From e13ef14d78c72cae1dfdf0d291228b807670c0f8 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 11:29:39 +0900
Subject: [PATCH v19 6/7] Refactor logic for page manipulations of sequence AMs

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

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

v19-0007-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From 6a989a95a2b6a2d743a6305d66a5a181573a244a Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 11:32:05 +0900
Subject: [PATCH v19 7/7] snowflake: Add sequence AM based on it

This includes documentation and a basic implementation, though this is
in a very early stage.
---
 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            | 510 +++++++++++++++++++++++
 contrib/snowflake/snowflake.control      |   5 +
 contrib/snowflake/sql/snowflake.sql      |  29 ++
 13 files changed, 798 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 ed30ee7d639f..e3bd2b464172 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..bb0270529852
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2025, 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 += bloom
+
+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..6acee691fafc
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,510 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2025, 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/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, (Item) 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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_snowflake_data seq;
+
+	seq = read_snowflake(rel, &buf, &seqdatatuple);
+	*last_value = seq->count;
+	*is_called = seq->is_called;
+	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

#44Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#43)
7 attachment(s)
Re: Sequence Access Methods, round two

On Mon, Sep 01, 2025 at 01:33:45PM +0900, Michael Paquier wrote:

Another rebase required due to the business with BufferGetPage() in
710e6c4301ee.

By the way, I have remembered the reason why I have introduced
AT_AddColumnToSequence as a new ALTER TABLE subcommands: ACL check for
sequences. Like views, this simplifies the object type check a lot.

Rebased as v20 due to the GUC changes in 63599896545c.
--
Michael

Attachments:

v20-0001-Integrate-addition-of-attributes-for-sequences-w.patchtext/x-diff; charset=us-asciiDownload
From 502d45d2abb47c61c77a9325db5a8bb81aeccb05 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v20 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               | 29 +++++++++++++++++--
 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, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 86a236bd58b1..58ee44e2b617 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2412,6 +2412,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 636d3c3ec737..0befbb2b5e12 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -136,6 +136,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;
@@ -174,7 +177,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;
@@ -198,7 +200,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;
@@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 082a3575d621..67f47bf63523 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4645,6 +4645,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 */
@@ -4940,6 +4941,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,
@@ -5372,6 +5380,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,
@@ -6587,6 +6596,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 5f442bc3bd4e..305847655787 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1658,7 +1658,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 50d0354a3417..ed31059ef584 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
@@ -70,7 +73,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 193669f2bc1e..254fdf90c79a 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

v20-0002-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From 20b71741e6bcfbcd2faa2640c233e96f446f2c4c Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 09:18:40 +0900
Subject: [PATCH v20 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.
- seqlocalxlog.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/seqlocalam.h               |  58 ++
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 .../rmgrdesc/{seqdesc.c => seqlocaldesc.c}    |  20 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/meson.build       |   2 +
 src/backend/access/sequence/seqlocalam.c      | 634 ++++++++++++++++++
 src/backend/access/sequence/seqlocalxlog.c    |  82 +++
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 634 +-----------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 14 files changed, 822 insertions(+), 622 deletions(-)
 create mode 100644 src/include/access/seqlocalam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%)
 create mode 100644 src/backend/access/sequence/seqlocalam.c
 create mode 100644 src/backend/access/sequence/seqlocalxlog.c

diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 8e7fc9db8778..b942b25350bc 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/seqlocalam.h b/src/include/access/seqlocalam.h
new file mode 100644
index 000000000000..7d75823a38c4
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2025, 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 "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_SEQ_LOCAL_LOG			0x00
+
+typedef struct xl_seq_local_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_seq_local_rec;
+
+/*
+ * 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;
+
+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);
+
+/* 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);
+extern void seq_local_change_persistence(Relation rel,
+										 char newrelpersistence);
+
+#endif							/* SEQLOCALAM_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 96c98e800c22..6b3b3e1e44c1 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 0d289d77fcf7..6ffbcb2c4735 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.c
  *
  * Portions Copyright (c) 1996-2025, 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.h"
+#include "access/seqlocalam.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..a15ceec1c0a0 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 ec9ab9b7e9db..8bc0e95e68c0 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2025, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'seqlocalam.c',
+  'seqlocalxlog.c',
   'sequence.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
new file mode 100644
index 000000000000..762f2dae3255
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -0,0 +1,634 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/multixact.h"
+#include "access/seqlocalam.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"
+
+
+/*
+ * 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
+
+/* 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, (Item) 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 "heap";
+}
+
+/*
+ * 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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_seq_local_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	*last_value = seq->last_value;
+	*is_called = seq->is_called;
+
+	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/sequence/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c
new file mode 100644
index 000000000000..2b8424514a51
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalxlog.c
@@ -0,0 +1,82 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalxlog.c
+ *	  WAL replay logic for 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/seqlocalxlog.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/seqlocalam.h"
+#include "access/xlogutils.h"
+#include "storage/block.h"
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+seq_local_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+seq_local_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record);
+	seq_local_magic *sm;
+
+	if (info != XLOG_SEQ_LOCAL_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	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_local_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec);
+
+	if (PageAddItem(localpage, (Item) item, itemsz,
+					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "seq_local_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 1b7499726eb0..cc92268937b1 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0befbb2b5e12..41cfa4f1b9a5 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -50,23 +51,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -96,13 +80,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,
@@ -134,14 +114,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
@@ -174,35 +148,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;
@@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	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)
@@ -292,10 +222,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;
@@ -306,7 +232,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))
@@ -315,40 +240,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 */
@@ -357,106 +250,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, (Item) 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
  *
@@ -468,10 +261,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;
@@ -480,7 +270,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	bool		reset_state = false;
 	bool		is_called;
 	int64		last_value;
-	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
 	relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -507,16 +296,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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -526,32 +307,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 */
@@ -580,8 +339,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
@@ -596,10 +353,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);
 }
@@ -662,24 +416,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);
@@ -724,105 +469,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;
@@ -832,69 +481,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;
@@ -984,9 +570,6 @@ do_setval(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,
@@ -1020,9 +603,6 @@ do_setval(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),
@@ -1044,37 +624,8 @@ do_setval(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);
 }
@@ -1215,62 +766,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
@@ -1855,16 +1350,13 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		Buffer		buf;
-		HeapTupleData seqtuple;
-		Form_pg_sequence_data seq;
+		bool		is_called;
+		int64		last_value;
 
-		seq = read_seq_tuple(seqrel, &buf, &seqtuple);
+		seq_local_get_state(seqrel, &last_value, &is_called);
 
-		values[0] = Int64GetDatum(seq->last_value);
-		values[1] = BoolGetDatum(seq->is_called);
-
-		UnlockReleaseBuffer(buf);
+		values[0] = Int64GetDatum(last_value);
+		values[1] = BoolGetDatum(is_called);
 	}
 	else
 		memset(isnull, true, sizeof(isnull));
@@ -1908,17 +1400,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);
 	}
+
 	sequence_close(seqrel, NoLock);
 
 	if (is_called)
@@ -1927,57 +1411,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
-
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, (Item) item, itemsz,
-					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1992,14 +1425,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c767e..8d1195de2637 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/seqlocaldesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index fac509ed134e..2fcf9fc4392a 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index f26d75e01cfd..2495a28d26a1 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

v20-0003-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From e74d928814f6d2056850377cef70f45aee20039e Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 11:40:10 +0900
Subject: [PATCH v20 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               |  15 --
 src/include/access/sequenceam.h               | 181 ++++++++++++++++++
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/seqlocalam.c      |  41 +++-
 src/backend/access/sequence/sequence.c        |   3 +-
 src/backend/access/sequence/sequenceamapi.c   | 145 ++++++++++++++
 src/backend/catalog/heap.c                    |   6 +-
 src/backend/commands/amcmds.c                 |  16 ++
 src/backend/commands/sequence.c               |  23 +--
 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, 693 insertions(+), 183 deletions(-)
 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
index 7d75823a38c4..5a0d77e392f4 100644
--- a/src/include/access/seqlocalam.h
+++ b/src/include/access/seqlocalam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_SEQ_LOCAL_LOG			0x00
@@ -41,18 +40,4 @@ 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);
 
-/* 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);
-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..a38e1d478a60
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,181 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2025, 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.
+	 */
+	void		(*get_state) (Relation rel, int64 *last_value, bool *is_called);
+
+	/*
+	 * 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)
+{
+	rel->rd_sequenceam->get_state(rel, last_value, is_called);
+}
+
+/*
+ * 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 26d15928a155..8f076fcec958 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 6e98a0930c27..080bea5031a9 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 07d182da796a..012a2863c037 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 118d6da1ace0..b272b4885e6e 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',
@@ -7894,6 +7900,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 cb730aeac864..d0054c5c988c 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 dd22b5efdfd9..6790728aced3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -145,6 +145,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 9ac0b67683d3..7693e9941fc9 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index ea36cb0fda40..c0311fbe8220 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 58ee44e2b617..9d2bd341d104 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3224,6 +3224,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 82ac8646a8d4..ab5e2eab0401 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 b552359915f1..4e418968253d 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 a15ceec1c0a0..62006165a15f 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = seqlocalam.o seqlocalxlog.o sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 8bc0e95e68c0..d82af34d538c 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -4,4 +4,5 @@ backend_sources += files(
   'seqlocalam.c',
   'seqlocalxlog.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
index 762f2dae3255..954749cd8fb6 100644
--- a/src/backend/access/sequence/seqlocalam.c
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -17,6 +17,7 @@
 
 #include "access/multixact.h"
 #include "access/seqlocalam.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -24,6 +25,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -230,10 +232,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;
@@ -417,7 +419,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 "heap";
@@ -432,7 +434,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];
@@ -499,7 +501,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;
@@ -547,7 +549,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;
@@ -600,7 +602,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)
 {
 	Buffer		buf;
@@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called)
  *
  * Persistence change for the local sequence Relation.
  */
-void
+static void
 seq_local_change_persistence(Relation rel, char newrelpersistence)
 {
 	Buffer		buf;
@@ -632,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 8b5303553702..f30317d1feae 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..a77f6da07cb1
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2025, 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/xact.h"
+#include "access/sequenceam.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 fd6537567ea2..e774dfa86e4e 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 58ed9d216cc0..e44633d13b60 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 41cfa4f1b9a5..e2ebede15116 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -152,6 +152,7 @@ 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;
@@ -173,7 +174,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)
@@ -241,7 +242,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 */
@@ -297,7 +298,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
 
 	/* Read sequence data */
-	seq_local_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -310,7 +311,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 */
@@ -353,7 +354,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);
 }
@@ -470,8 +471,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;
@@ -625,7 +626,7 @@ do_setval(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);
 }
@@ -1353,7 +1354,7 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		bool		is_called;
 		int64		last_value;
 
-		seq_local_get_state(seqrel, &last_value, &is_called);
+		sequence_get_state(seqrel, &last_value, &is_called);
 
 		values[0] = Int64GetDatum(last_value);
 		values[1] = BoolGetDatum(is_called);
@@ -1400,7 +1401,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		seq_local_get_state(seqrel, &result, &is_called);
+		sequence_get_state(seqrel, &result, &is_called);
 	}
 
 	sequence_close(seqrel, NoLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 67f47bf63523..cb12351889bf 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"
@@ -1025,14 +1026,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)
 	{
@@ -1045,6 +1050,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 9ecddb142314..0c79454f641e 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 9fd48acb1f8e..992870b43b93 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -381,6 +381,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
@@ -4982,23 +4983,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;
@@ -5035,6 +5039,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); }
 		;
@@ -6038,6 +6047,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 afcf54169c3b..a8fb08752f91 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"
@@ -518,6 +519,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 317a1f2b282f..68f160dda7d3 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 6fe268a8eec1..f3fd8d26b185 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);
 
@@ -1831,17 +1835,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.
@@ -1872,6 +1868,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
  *
@@ -3711,14 +3750,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
@@ -4343,13 +4385,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);
@@ -6425,8 +6475,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);
@@ -6438,6 +6490,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 a157cec3c4d0..ed5900c4fd62 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2704,6 +2704,14 @@
   check_hook => 'check_default_table_access_method',
 },
 
+{ 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_tablespace', type => 'string', context => 'PGC_USERSET', group => 'CLIENT_CONN_STATEMENT',
   short_desc => 'Sets the default tablespace to create tables and indexes in.',
   long_desc => 'An empty string means use the database\'s default tablespace.',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 787933a9e5ac..58a9710841ff 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 a9d8293474af..af0c0886adcc 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -750,6 +750,7 @@ autovacuum_worker_slots = 16	# autovacuum worker slots to allocate
 					#   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 4aa793d7de75..44d1323cdce8 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 6b20a4404b21..b7dca7b3b02d 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2597,7 +2597,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");
@@ -3366,7 +3366,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");
@@ -3664,7 +3664,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 20bf9ea9cdf7..20b3d8520315 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1943,6 +1943,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 a79325e8a2f7..19ade0eb7e62 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5129,31 +5129,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
@@ -5178,32 +5180,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 943e56506bf1..f36e267c4b98 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 2fb3a8528781..e60eeab33c02 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 df795759bb4c..dbaceb9fd2c8 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 a13e81628902..56b9e02aae36 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2692,6 +2692,7 @@ SeqScanState
 SeqTable
 SeqTableData
 SeqType
+SequenceAmRoutine
 SequenceItem
 SerCommitSeqNo
 SerialControl
@@ -3763,6 +3764,7 @@ list_sort_comparator
 loc_chunk
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 local_ts_iter
 local_ts_radix_tree
@@ -4050,7 +4052,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
@@ -4277,6 +4278,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
@@ -4288,7 +4290,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

v20-0004-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From e21408ad7a6a60f85d5328d85c7d82a7b6b3e9e1 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 15 Aug 2025 14:35:50 +0900
Subject: [PATCH v20 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 058b5d659bac..77ab1958d8f6 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;
@@ -2420,6 +2422,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 */
 
@@ -2687,6 +2690,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);
@@ -2791,6 +2795,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);
 
@@ -3500,6 +3507,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;
 
@@ -3662,6 +3672,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.
  */
@@ -3871,6 +3932,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);
 
@@ -4391,6 +4453,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);
@@ -5130,6 +5194,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -5189,6 +5254,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 bea793456f96..a55c412b96c2 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},
@@ -1232,6 +1234,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;
@@ -1354,6 +1357,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 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"));
@@ -14355,6 +14359,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;
@@ -18838,26 +18845,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);
@@ -18877,6 +18898,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);
@@ -18948,6 +18973,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);
 	}
@@ -19070,6 +19096,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 bb451c1bae14..2d52d96514d6 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},
@@ -450,6 +452,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)
@@ -731,6 +735,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 c9776306c5c4..b2e25da92b92 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},
@@ -435,6 +437,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;
@@ -566,6 +569,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 e7a2d64f7413..e9f719460400 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -650,6 +650,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_table_access_method => {
 		dump_cmd => [
 			'pg_dump', '--no-sync',
@@ -873,6 +882,7 @@ my %full_runs = (
 	no_policies => 1,
 	no_privs => 1,
 	no_statistics => 1,
+	no_sequence_access_method => 1,
 	no_table_access_method => 1,
 	pg_dumpall_dbprivs => 1,
 	pg_dumpall_exclude => 1,
@@ -4911,6 +4921,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
@@ -4939,6 +4961,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 fd4ecf01a0a0..895e324ba193 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 9f639f61db02..94a5bad771b2 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

v20-0005-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From 6dd7b9cea5c78b29afd87cb66a35946e47a95b5e Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v20 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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 0a4b3e55ba5e..19b0c94b327d 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9762,6 +9762,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 af476c82fcc1..9f369cc89930 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..3067dc4d4df0 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>SEQUENCE</literal> and
+      <literal>INDEX</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 1e283f13d15c..52c6096e4ba2 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..a96170bfac03
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.51.0

v20-0006-Refactor-logic-for-page-manipulations-of-sequenc.patchtext/x-diff; charset=us-asciiDownload
From d853379a7969d00bc78fc1fcb9450bd98a4b9821 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 11:29:39 +0900
Subject: [PATCH v20 6/7] Refactor logic for page manipulations of sequence AMs

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

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

v20-0007-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From 2dca6b33a6898533fb8d200583802f35237faf56 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 11:32:05 +0900
Subject: [PATCH v20 7/7] snowflake: Add sequence AM based on it

This includes documentation and a basic implementation, though this is
in a very early stage.
---
 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            | 510 +++++++++++++++++++++++
 contrib/snowflake/snowflake.control      |   5 +
 contrib/snowflake/sql/snowflake.sql      |  29 ++
 13 files changed, 798 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 ed30ee7d639f..e3bd2b464172 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..bb0270529852
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2025, 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 += bloom
+
+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..6acee691fafc
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,510 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2025, 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/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, (Item) 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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_snowflake_data seq;
+
+	seq = read_snowflake(rel, &buf, &seqdatatuple);
+	*last_value = seq->count;
+	*is_called = seq->is_called;
+	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

#45Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#44)
7 attachment(s)
Re: Sequence Access Methods, round two

On Thu, Sep 04, 2025 at 12:15:24PM +0900, Michael Paquier wrote:

Rebased as v20 due to the GUC changes in 63599896545c.

Rebased as v21 due to conflicts in the TAP tests of pg_dump.
--
Michael

Attachments:

v21-0001-Integrate-addition-of-attributes-for-sequences-w.patchtext/x-diff; charset=us-asciiDownload
From 37e0c70d93062bae6b57bdd5ed21196701b8d9d5 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v21 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               | 29 +++++++++++++++++--
 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, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 87c1086ec995..1687e090e512 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2415,6 +2415,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 636d3c3ec737..0befbb2b5e12 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -136,6 +136,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;
@@ -174,7 +177,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;
@@ -198,7 +200,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;
@@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 fc89352b6617..564a8a2e4e54 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4649,6 +4649,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 */
@@ -4944,6 +4945,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,
@@ -5376,6 +5384,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,
@@ -6591,6 +6600,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 918db53dd5e7..2dca1c40e176 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1658,7 +1658,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 50d0354a3417..ed31059ef584 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
@@ -70,7 +73,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 193669f2bc1e..254fdf90c79a 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

v21-0002-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From b9fd39936e32723c857eb5ff5531115c00736d5a Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 09:18:40 +0900
Subject: [PATCH v21 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.
- seqlocalxlog.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/seqlocalam.h               |  58 ++
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 .../rmgrdesc/{seqdesc.c => seqlocaldesc.c}    |  20 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/meson.build       |   2 +
 src/backend/access/sequence/seqlocalam.c      | 634 ++++++++++++++++++
 src/backend/access/sequence/seqlocalxlog.c    |  82 +++
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 634 +-----------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 14 files changed, 822 insertions(+), 622 deletions(-)
 create mode 100644 src/include/access/seqlocalam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%)
 create mode 100644 src/backend/access/sequence/seqlocalam.c
 create mode 100644 src/backend/access/sequence/seqlocalxlog.c

diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 8e7fc9db8778..b942b25350bc 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/seqlocalam.h b/src/include/access/seqlocalam.h
new file mode 100644
index 000000000000..7d75823a38c4
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2025, 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 "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_SEQ_LOCAL_LOG			0x00
+
+typedef struct xl_seq_local_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_seq_local_rec;
+
+/*
+ * 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;
+
+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);
+
+/* 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);
+extern void seq_local_change_persistence(Relation rel,
+										 char newrelpersistence);
+
+#endif							/* SEQLOCALAM_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 96c98e800c22..6b3b3e1e44c1 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 0d289d77fcf7..6ffbcb2c4735 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.c
  *
  * Portions Copyright (c) 1996-2025, 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.h"
+#include "access/seqlocalam.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..a15ceec1c0a0 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 ec9ab9b7e9db..8bc0e95e68c0 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2025, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'seqlocalam.c',
+  'seqlocalxlog.c',
   'sequence.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
new file mode 100644
index 000000000000..762f2dae3255
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -0,0 +1,634 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/multixact.h"
+#include "access/seqlocalam.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"
+
+
+/*
+ * 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
+
+/* 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, (Item) 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 "heap";
+}
+
+/*
+ * 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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_pg_seq_local_data seq;
+
+	/* lock page buffer and read tuple */
+	seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+	*last_value = seq->last_value;
+	*is_called = seq->is_called;
+
+	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/sequence/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c
new file mode 100644
index 000000000000..2b8424514a51
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalxlog.c
@@ -0,0 +1,82 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalxlog.c
+ *	  WAL replay logic for 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/seqlocalxlog.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/seqlocalam.h"
+#include "access/xlogutils.h"
+#include "storage/block.h"
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+seq_local_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+seq_local_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record);
+	seq_local_magic *sm;
+
+	if (info != XLOG_SEQ_LOCAL_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	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_local_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec);
+
+	if (PageAddItem(localpage, (Item) item, itemsz,
+					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "seq_local_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 1b7499726eb0..cc92268937b1 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0befbb2b5e12..41cfa4f1b9a5 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -50,23 +51,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -96,13 +80,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,
@@ -134,14 +114,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
@@ -174,35 +148,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;
@@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	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)
@@ -292,10 +222,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;
@@ -306,7 +232,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))
@@ -315,40 +240,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 */
@@ -357,106 +250,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, (Item) 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
  *
@@ -468,10 +261,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;
@@ -480,7 +270,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	bool		reset_state = false;
 	bool		is_called;
 	int64		last_value;
-	HeapTuple	newdatatuple;
 
 	/* Open and lock sequence, and check for ownership along the way. */
 	relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -507,16 +296,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);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -526,32 +307,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 */
@@ -580,8 +339,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
@@ -596,10 +353,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);
 }
@@ -662,24 +416,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);
@@ -724,105 +469,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;
@@ -832,69 +481,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;
@@ -984,9 +570,6 @@ do_setval(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,
@@ -1020,9 +603,6 @@ do_setval(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),
@@ -1044,37 +624,8 @@ do_setval(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);
 }
@@ -1215,62 +766,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
@@ -1855,16 +1350,13 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		Buffer		buf;
-		HeapTupleData seqtuple;
-		Form_pg_sequence_data seq;
+		bool		is_called;
+		int64		last_value;
 
-		seq = read_seq_tuple(seqrel, &buf, &seqtuple);
+		seq_local_get_state(seqrel, &last_value, &is_called);
 
-		values[0] = Int64GetDatum(seq->last_value);
-		values[1] = BoolGetDatum(seq->is_called);
-
-		UnlockReleaseBuffer(buf);
+		values[0] = Int64GetDatum(last_value);
+		values[1] = BoolGetDatum(is_called);
 	}
 	else
 		memset(isnull, true, sizeof(isnull));
@@ -1908,17 +1400,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);
 	}
+
 	sequence_close(seqrel, NoLock);
 
 	if (is_called)
@@ -1927,57 +1411,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
-
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, (Item) item, itemsz,
-					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1992,14 +1425,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c767e..8d1195de2637 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/seqlocaldesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index fac509ed134e..2fcf9fc4392a 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index f26d75e01cfd..2495a28d26a1 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

v21-0003-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From b93b43b98d357137b3d0af8ebeea5f6c6654cd33 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 11:40:10 +0900
Subject: [PATCH v21 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               |  15 --
 src/include/access/sequenceam.h               | 181 ++++++++++++++++++
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/seqlocalam.c      |  41 +++-
 src/backend/access/sequence/sequence.c        |   3 +-
 src/backend/access/sequence/sequenceamapi.c   | 145 ++++++++++++++
 src/backend/catalog/heap.c                    |   6 +-
 src/backend/commands/amcmds.c                 |  16 ++
 src/backend/commands/sequence.c               |  23 +--
 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, 693 insertions(+), 183 deletions(-)
 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
index 7d75823a38c4..5a0d77e392f4 100644
--- a/src/include/access/seqlocalam.h
+++ b/src/include/access/seqlocalam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_SEQ_LOCAL_LOG			0x00
@@ -41,18 +40,4 @@ 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);
 
-/* 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);
-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..a38e1d478a60
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,181 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2025, 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.
+	 */
+	void		(*get_state) (Relation rel, int64 *last_value, bool *is_called);
+
+	/*
+	 * 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)
+{
+	rel->rd_sequenceam->get_state(rel, last_value, is_called);
+}
+
+/*
+ * 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 26d15928a155..8f076fcec958 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 6e98a0930c27..080bea5031a9 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 07d182da796a..012a2863c037 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 01eba3b5a190..008c3217c16b 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',
@@ -7906,6 +7912,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 cb730aeac864..d0054c5c988c 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 dd22b5efdfd9..6790728aced3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -145,6 +145,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 9ac0b67683d3..7693e9941fc9 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index ea36cb0fda40..c0311fbe8220 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 1687e090e512..97335a79031b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3227,6 +3227,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 82ac8646a8d4..ab5e2eab0401 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 219904363737..d4ebae417a57 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 a15ceec1c0a0..62006165a15f 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = seqlocalam.o seqlocalxlog.o sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 8bc0e95e68c0..d82af34d538c 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -4,4 +4,5 @@ backend_sources += files(
   'seqlocalam.c',
   'seqlocalxlog.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
index 762f2dae3255..954749cd8fb6 100644
--- a/src/backend/access/sequence/seqlocalam.c
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -17,6 +17,7 @@
 
 #include "access/multixact.h"
 #include "access/seqlocalam.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -24,6 +25,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -230,10 +232,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;
@@ -417,7 +419,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 "heap";
@@ -432,7 +434,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];
@@ -499,7 +501,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;
@@ -547,7 +549,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;
@@ -600,7 +602,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)
 {
 	Buffer		buf;
@@ -621,7 +623,7 @@ seq_local_get_state(Relation rel, int64 *last_value, bool *is_called)
  *
  * Persistence change for the local sequence Relation.
  */
-void
+static void
 seq_local_change_persistence(Relation rel, char newrelpersistence)
 {
 	Buffer		buf;
@@ -632,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 8b5303553702..f30317d1feae 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..a77f6da07cb1
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2025, 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/xact.h"
+#include "access/sequenceam.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 fd6537567ea2..e774dfa86e4e 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 58ed9d216cc0..e44633d13b60 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 41cfa4f1b9a5..e2ebede15116 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -152,6 +152,7 @@ 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;
@@ -173,7 +174,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)
@@ -241,7 +242,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 */
@@ -297,7 +298,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
 	seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
 
 	/* Read sequence data */
-	seq_local_get_state(seqrel, &last_value, &is_called);
+	sequence_get_state(seqrel, &last_value, &is_called);
 
 	/* Check and set new values */
 	init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -310,7 +311,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 */
@@ -353,7 +354,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);
 }
@@ -470,8 +471,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;
@@ -625,7 +626,7 @@ do_setval(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);
 }
@@ -1353,7 +1354,7 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 		bool		is_called;
 		int64		last_value;
 
-		seq_local_get_state(seqrel, &last_value, &is_called);
+		sequence_get_state(seqrel, &last_value, &is_called);
 
 		values[0] = Int64GetDatum(last_value);
 		values[1] = BoolGetDatum(is_called);
@@ -1400,7 +1401,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		!RELATION_IS_OTHER_TEMP(seqrel) &&
 		(RelationIsPermanent(seqrel) || !RecoveryInProgress()))
 	{
-		seq_local_get_state(seqrel, &result, &is_called);
+		sequence_get_state(seqrel, &result, &is_called);
 	}
 
 	sequence_close(seqrel, NoLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 564a8a2e4e54..a74fff4c7df6 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"
@@ -1026,14 +1027,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)
 	{
@@ -1046,6 +1051,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 9ecddb142314..0c79454f641e 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 57bf7a7c7f2c..94ccec95551e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -382,6 +382,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
@@ -4983,23 +4984,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;
@@ -5036,6 +5040,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); }
 		;
@@ -6039,6 +6048,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 e96b38a59d50..7678e6e2880f 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"
@@ -518,6 +519,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 317a1f2b282f..68f160dda7d3 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 2b798b823ea5..d11a2f2e2228 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);
 
@@ -1831,17 +1835,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.
@@ -1872,6 +1868,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
  *
@@ -3711,14 +3750,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
@@ -4343,13 +4385,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);
@@ -6425,8 +6475,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);
@@ -6438,6 +6490,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 6bc6be13d2ad..c00914a678df 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2710,6 +2710,14 @@
   check_hook => 'check_default_table_access_method',
 },
 
+{ 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_tablespace', type => 'string', context => 'PGC_USERSET', group => 'CLIENT_CONN_STATEMENT',
   short_desc => 'Sets the default tablespace to create tables and indexes in.',
   long_desc => 'An empty string means use the database\'s default tablespace.',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 00c8376cf4de..905db5630ec4 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 c36fcb9ab610..050da1a6e4e5 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -751,6 +751,7 @@ autovacuum_worker_slots = 16	# autovacuum worker slots to allocate
 					#   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 4aa793d7de75..44d1323cdce8 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 6176741d20b1..e41524542f7f 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2607,7 +2607,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");
@@ -3368,7 +3368,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");
@@ -3666,7 +3666,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 20bf9ea9cdf7..20b3d8520315 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1943,6 +1943,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 a79325e8a2f7..19ade0eb7e62 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5129,31 +5129,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
@@ -5178,32 +5180,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 943e56506bf1..f36e267c4b98 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 2fb3a8528781..e60eeab33c02 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 df795759bb4c..dbaceb9fd2c8 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 37f26f6c6b75..60eabde0ece3 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2695,6 +2695,7 @@ SeqScanState
 SeqTable
 SeqTableData
 SeqType
+SequenceAmRoutine
 SequenceItem
 SerCommitSeqNo
 SerialControl
@@ -3761,6 +3762,7 @@ list_sort_comparator
 loc_chunk
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 local_ts_iter
 local_ts_radix_tree
@@ -4048,7 +4050,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
@@ -4275,6 +4276,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
@@ -4286,7 +4288,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

v21-0004-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From 831476d844490b878de5af695b6e180b41638f97 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 15 Aug 2025 14:35:50 +0900
Subject: [PATCH v21 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 59eaecb4ed71..39420da1661e 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;
@@ -2420,6 +2422,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 */
 
@@ -2687,6 +2690,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);
@@ -2791,6 +2795,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);
 
@@ -3536,6 +3543,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;
 
@@ -3698,6 +3708,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.
  */
@@ -3907,6 +3968,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);
 
@@ -4427,6 +4489,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);
@@ -5166,6 +5230,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -5225,6 +5290,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 9fc3671cb350..90ce40b9a383 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},
@@ -1249,6 +1251,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;
@@ -1371,6 +1374,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 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"));
@@ -14409,6 +14413,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;
@@ -18892,26 +18899,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);
@@ -18931,6 +18952,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);
@@ -19002,6 +19027,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);
 	}
@@ -19124,6 +19150,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 bb451c1bae14..2d52d96514d6 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},
@@ -450,6 +452,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)
@@ -731,6 +735,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 c9776306c5c4..b2e25da92b92 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},
@@ -435,6 +437,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;
@@ -566,6 +569,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 fc5b9b52f804..0f6c20224b08 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -667,6 +667,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',
@@ -917,6 +926,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,
@@ -5001,6 +5011,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
@@ -5029,6 +5051,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 fd4ecf01a0a0..895e324ba193 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 9f639f61db02..94a5bad771b2 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

v21-0005-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From 05ca60e7d869d100bb1e4a13df9402a24b1193e3 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v21 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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 e9b420f3ddbe..15bff27f27d3 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9768,6 +9768,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 af476c82fcc1..9f369cc89930 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..3067dc4d4df0 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>SEQUENCE</literal> and
+      <literal>INDEX</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 1e283f13d15c..52c6096e4ba2 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..a96170bfac03
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.51.0

v21-0006-Refactor-logic-for-page-manipulations-of-sequenc.patchtext/x-diff; charset=us-asciiDownload
From 34eb8fa93809ef9d1d51935e4b4b93148e911d60 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 11:29:39 +0900
Subject: [PATCH v21 6/7] Refactor logic for page manipulations of sequence AMs

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

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

v21-0007-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From 6f775d1fec1e519f76b251b8bb03e8979d3135ee Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 11:32:05 +0900
Subject: [PATCH v21 7/7] snowflake: Add sequence AM based on it

This includes documentation and a basic implementation, though this is
in a very early stage.
---
 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            | 510 +++++++++++++++++++++++
 contrib/snowflake/snowflake.control      |   5 +
 contrib/snowflake/sql/snowflake.sql      |  29 ++
 13 files changed, 798 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 ed30ee7d639f..e3bd2b464172 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..bb0270529852
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2025, 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 += bloom
+
+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..6acee691fafc
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,510 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2025, 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/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, (Item) 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)
+{
+	Buffer		buf;
+	HeapTupleData seqdatatuple;
+	Form_snowflake_data seq;
+
+	seq = read_snowflake(rel, &buf, &seqdatatuple);
+	*last_value = seq->count;
+	*is_called = seq->is_called;
+	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

#46Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#45)
7 attachment(s)
Re: Sequence Access Methods, round two

On Fri, Oct 03, 2025 at 05:24:29PM +0900, Michael Paquier wrote:

Rebased as v21 due to conflicts in the TAP tests of pg_dump.

A few more conflicts, with htup_details.h and the addition of the page
LSN to pg_get_sequence_data(). Patch set now at v22.
--
Michael

Attachments:

v22-0001-Integrate-addition-of-attributes-for-sequences-w.patchtext/x-diff; charset=us-asciiDownload
From a073837855cf5ea34218ab0ca8c39996c8b9b104 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v22 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               | 29 +++++++++++++++++--
 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, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 87c1086ec995..1687e090e512 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2415,6 +2415,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 cf46a5433648..7ff12fd0d2a5 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -137,6 +137,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;
@@ -175,7 +178,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;
@@ -199,7 +201,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;
@@ -209,12 +211,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 fc89352b6617..564a8a2e4e54 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4649,6 +4649,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 */
@@ -4944,6 +4945,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,
@@ -5376,6 +5384,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,
@@ -6591,6 +6600,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 918db53dd5e7..2dca1c40e176 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1658,7 +1658,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 50d0354a3417..ed31059ef584 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
@@ -70,7 +73,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 193669f2bc1e..254fdf90c79a 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

v22-0002-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From bc17ca776d789240d56fdd59c2e913d4d17905e3 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 09:18:40 +0900
Subject: [PATCH v22 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.
- seqlocalxlog.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/seqlocalam.h               |  58 ++
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 .../rmgrdesc/{seqdesc.c => seqlocaldesc.c}    |  20 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/meson.build       |   2 +
 src/backend/access/sequence/seqlocalam.c      | 641 +++++++++++++++++
 src/backend/access/sequence/seqlocalxlog.c    |  82 +++
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 642 +-----------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 14 files changed, 834 insertions(+), 625 deletions(-)
 create mode 100644 src/include/access/seqlocalam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%)
 create mode 100644 src/backend/access/sequence/seqlocalam.c
 create mode 100644 src/backend/access/sequence/seqlocalxlog.c

diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 8e7fc9db8778..b942b25350bc 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/seqlocalam.h b/src/include/access/seqlocalam.h
new file mode 100644
index 000000000000..07eeb3af8713
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2025, 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 "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_SEQ_LOCAL_LOG			0x00
+
+typedef struct xl_seq_local_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_seq_local_rec;
+
+/*
+ * 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;
+
+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);
+
+/* 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/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 96c98e800c22..6b3b3e1e44c1 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 0d289d77fcf7..6ffbcb2c4735 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.c
  *
  * Portions Copyright (c) 1996-2025, 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.h"
+#include "access/seqlocalam.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..a15ceec1c0a0 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 ec9ab9b7e9db..8bc0e95e68c0 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2025, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'seqlocalam.c',
+  'seqlocalxlog.c',
   'sequence.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
new file mode 100644
index 000000000000..92c0d85c0289
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -0,0 +1,641 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/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"
+
+
+/*
+ * 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
+
+/* 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, (Item) 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 "heap";
+}
+
+/*
+ * 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/sequence/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c
new file mode 100644
index 000000000000..2b8424514a51
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalxlog.c
@@ -0,0 +1,82 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalxlog.c
+ *	  WAL replay logic for 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/seqlocalxlog.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/seqlocalam.h"
+#include "access/xlogutils.h"
+#include "storage/block.h"
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+seq_local_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+seq_local_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record);
+	seq_local_magic *sm;
+
+	if (info != XLOG_SEQ_LOCAL_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	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_local_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec);
+
+	if (PageAddItem(localpage, (Item) item, itemsz,
+					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "seq_local_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 1b7499726eb0..cc92268937b1 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 7ff12fd0d2a5..b92ad8d29e58 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -51,23 +52,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -97,13 +81,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,
@@ -135,14 +115,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
@@ -175,35 +149,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;
@@ -216,35 +161,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	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)
@@ -293,10 +223,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;
@@ -307,7 +233,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))
@@ -316,40 +241,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 */
@@ -358,106 +251,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, (Item) 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
  *
@@ -469,10 +262,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;
@@ -481,7 +271,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,
@@ -508,16 +298,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,
@@ -527,32 +309,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 */
@@ -581,8 +341,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
@@ -597,10 +355,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);
 }
@@ -663,24 +418,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);
@@ -725,105 +471,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;
@@ -833,69 +483,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;
@@ -985,9 +572,6 @@ do_setval(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,
@@ -1021,9 +605,6 @@ do_setval(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),
@@ -1045,37 +626,8 @@ do_setval(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);
 }
@@ -1216,62 +768,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
@@ -1858,19 +1354,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));
@@ -1897,6 +1390,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);
@@ -1914,17 +1408,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)
@@ -1933,57 +1419,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 }
 
-
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, (Item) item, itemsz,
-					FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1998,14 +1433,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c767e..8d1195de2637 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/seqlocaldesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index fac509ed134e..2fcf9fc4392a 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index f26d75e01cfd..2495a28d26a1 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

v22-0003-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From ca0c4317c933d14bbcd71bc8c6f1993c314e771a Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 11:40:10 +0900
Subject: [PATCH v22 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               |  15 --
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/seqlocalam.c      |  41 +++-
 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               |  24 +--
 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, 697 insertions(+), 184 deletions(-)
 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
index 07eeb3af8713..5a0d77e392f4 100644
--- a/src/include/access/seqlocalam.h
+++ b/src/include/access/seqlocalam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_SEQ_LOCAL_LOG			0x00
@@ -41,18 +40,4 @@ 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);
 
-/* 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..1cfc28ca62e1
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,184 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2025, 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 26d15928a155..8f076fcec958 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 6e98a0930c27..080bea5031a9 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 07d182da796a..012a2863c037 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 7c20180637f6..444d43f0e814 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',
@@ -7910,6 +7916,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 cb730aeac864..d0054c5c988c 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 dd22b5efdfd9..6790728aced3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -145,6 +145,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 9ac0b67683d3..7693e9941fc9 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index ea36cb0fda40..c0311fbe8220 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 1687e090e512..97335a79031b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3227,6 +3227,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 82ac8646a8d4..ab5e2eab0401 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 219904363737..d4ebae417a57 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 a15ceec1c0a0..62006165a15f 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = seqlocalam.o seqlocalxlog.o sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 8bc0e95e68c0..d82af34d538c 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -4,4 +4,5 @@ backend_sources += files(
   'seqlocalam.c',
   'seqlocalxlog.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
index 92c0d85c0289..94a8de15547e 100644
--- a/src/backend/access/sequence/seqlocalam.c
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -18,6 +18,7 @@
 #include "access/htup_details.h"
 #include "access/multixact.h"
 #include "access/seqlocalam.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -25,6 +26,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -231,10 +233,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;
@@ -418,7 +420,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 "heap";
@@ -433,7 +435,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];
@@ -500,7 +502,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;
@@ -548,7 +550,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;
@@ -601,7 +603,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,
@@ -628,7 +630,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;
@@ -639,3 +641,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 8b5303553702..f30317d1feae 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..c3b43507f9af
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,146 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2025, 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 fd6537567ea2..e774dfa86e4e 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 58ed9d216cc0..e44633d13b60 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 b92ad8d29e58..c37c19202956 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -153,6 +153,7 @@ 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;
@@ -174,7 +175,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)
@@ -242,7 +243,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 */
@@ -299,7 +300,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,
@@ -312,7 +313,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 */
@@ -355,7 +356,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);
 }
@@ -472,8 +473,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;
@@ -627,7 +628,7 @@ do_setval(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);
 }
@@ -1358,8 +1359,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);
@@ -1408,7 +1408,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 564a8a2e4e54..a74fff4c7df6 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"
@@ -1026,14 +1027,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)
 	{
@@ -1046,6 +1051,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 9ecddb142314..0c79454f641e 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 57bf7a7c7f2c..94ccec95551e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -382,6 +382,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
@@ -4983,23 +4984,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;
@@ -5036,6 +5040,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); }
 		;
@@ -6039,6 +6048,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 e96b38a59d50..7678e6e2880f 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"
@@ -518,6 +519,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 317a1f2b282f..68f160dda7d3 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 2b798b823ea5..d11a2f2e2228 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);
 
@@ -1831,17 +1835,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.
@@ -1872,6 +1868,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
  *
@@ -3711,14 +3750,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
@@ -4343,13 +4385,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);
@@ -6425,8 +6475,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);
@@ -6438,6 +6490,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 6bc6be13d2ad..c00914a678df 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2710,6 +2710,14 @@
   check_hook => 'check_default_table_access_method',
 },
 
+{ 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_tablespace', type => 'string', context => 'PGC_USERSET', group => 'CLIENT_CONN_STATEMENT',
   short_desc => 'Sets the default tablespace to create tables and indexes in.',
   long_desc => 'An empty string means use the database\'s default tablespace.',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 00c8376cf4de..905db5630ec4 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 c36fcb9ab610..050da1a6e4e5 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -751,6 +751,7 @@ autovacuum_worker_slots = 16	# autovacuum worker slots to allocate
 					#   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 4aa793d7de75..44d1323cdce8 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 6176741d20b1..e41524542f7f 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2607,7 +2607,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");
@@ -3368,7 +3368,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");
@@ -3666,7 +3666,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 20bf9ea9cdf7..20b3d8520315 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1943,6 +1943,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 a79325e8a2f7..19ade0eb7e62 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5129,31 +5129,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
@@ -5178,32 +5180,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 943e56506bf1..f36e267c4b98 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 2fb3a8528781..e60eeab33c02 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 df795759bb4c..dbaceb9fd2c8 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 37f26f6c6b75..60eabde0ece3 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2695,6 +2695,7 @@ SeqScanState
 SeqTable
 SeqTableData
 SeqType
+SequenceAmRoutine
 SequenceItem
 SerCommitSeqNo
 SerialControl
@@ -3761,6 +3762,7 @@ list_sort_comparator
 loc_chunk
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 local_ts_iter
 local_ts_radix_tree
@@ -4048,7 +4050,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
@@ -4275,6 +4276,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
@@ -4286,7 +4288,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

v22-0004-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From 3af682cd2d2e46694840286a0eefec5a452401cc Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 15 Aug 2025 14:35:50 +0900
Subject: [PATCH v22 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 59eaecb4ed71..39420da1661e 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;
@@ -2420,6 +2422,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 */
 
@@ -2687,6 +2690,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);
@@ -2791,6 +2795,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);
 
@@ -3536,6 +3543,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;
 
@@ -3698,6 +3708,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.
  */
@@ -3907,6 +3968,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);
 
@@ -4427,6 +4489,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);
@@ -5166,6 +5230,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -5225,6 +5290,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 9fc3671cb350..90ce40b9a383 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},
@@ -1249,6 +1251,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;
@@ -1371,6 +1374,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 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"));
@@ -14409,6 +14413,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;
@@ -18892,26 +18899,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);
@@ -18931,6 +18952,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);
@@ -19002,6 +19027,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);
 	}
@@ -19124,6 +19150,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 bb451c1bae14..2d52d96514d6 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},
@@ -450,6 +452,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)
@@ -731,6 +735,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 c9776306c5c4..b2e25da92b92 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},
@@ -435,6 +437,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;
@@ -566,6 +569,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 fc5b9b52f804..0f6c20224b08 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -667,6 +667,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',
@@ -917,6 +926,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,
@@ -5001,6 +5011,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
@@ -5029,6 +5051,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 fd4ecf01a0a0..895e324ba193 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 9f639f61db02..94a5bad771b2 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

v22-0005-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From 785c4369ff6acaf2292a17b781b5b63a1e594c4b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v22 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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 e9b420f3ddbe..15bff27f27d3 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9768,6 +9768,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 af476c82fcc1..9f369cc89930 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..3067dc4d4df0 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>SEQUENCE</literal> and
+      <literal>INDEX</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 1e283f13d15c..52c6096e4ba2 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..a96170bfac03
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.51.0

v22-0006-Refactor-logic-for-page-manipulations-of-sequenc.patchtext/x-diff; charset=us-asciiDownload
From e8c8c19a3ad33e324da65ac4a922c1c642518df7 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 11:29:39 +0900
Subject: [PATCH v22 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 94a8de15547e..7d383b865864 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/seqlocalam.h"
 #include "access/sequenceam.h"
+#include "access/sequence_page.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -83,27 +84,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
@@ -122,8 +107,6 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 		MarkBufferDirtyHint(*buf, true);
 	}
 
-	seq = (Form_pg_seq_local_data) GETSTRUCT(seqdatatuple);
-
 	return seq;
 }
 
@@ -162,33 +145,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

v22-0007-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From 0b5d9ab695a2d5407a8d6e59fabc9f6b8a0df668 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 7 Oct 2025 16:32:09 +0900
Subject: [PATCH v22 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 ed30ee7d639f..e3bd2b464172 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..bb0270529852
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2025, 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 += bloom
+
+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..b42e89364e1f
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,519 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2025, 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, (Item) 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

#47Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#46)
7 attachment(s)
Re: Sequence Access Methods, round two

On Tue, Oct 07, 2025 at 04:46:36PM +0900, Michael Paquier wrote:

A few more conflicts, with htup_details.h and the addition of the page
LSN to pg_get_sequence_data(). Patch set now at v22.

Rebased due to conflicts with 76acf4b722fa.
--
Michael

Attachments:

v23-0001-Integrate-addition-of-attributes-for-sequences-w.patchtext/x-diff; charset=us-asciiDownload
From abb6fd9a876575cd901ed9329e3a1988961c7d9f Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v23 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               | 29 +++++++++++++++++--
 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, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ecbddd12e1b3..15e13b05230b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2415,6 +2415,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 c23dee5231c0..0c6d267218ee 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -137,6 +137,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;
@@ -175,7 +178,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;
@@ -199,7 +201,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;
@@ -209,12 +211,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 5fd8b51312c8..b68e3d161b86 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4648,6 +4648,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 */
@@ -4943,6 +4944,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,
@@ -5375,6 +5383,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,
@@ -6590,6 +6599,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 918db53dd5e7..2dca1c40e176 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1658,7 +1658,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 50d0354a3417..ed31059ef584 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
@@ -70,7 +73,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 193669f2bc1e..254fdf90c79a 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

v23-0002-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From bbe90845831a7c63a44278c818e65bc1e6c2a15c Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 09:18:40 +0900
Subject: [PATCH v23 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.
- seqlocalxlog.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/seqlocalam.h               |  58 ++
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 .../rmgrdesc/{seqdesc.c => seqlocaldesc.c}    |  20 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/meson.build       |   2 +
 src/backend/access/sequence/seqlocalam.c      | 640 ++++++++++++++++++
 src/backend/access/sequence/seqlocalxlog.c    |  81 +++
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 639 +----------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 14 files changed, 832 insertions(+), 622 deletions(-)
 create mode 100644 src/include/access/seqlocalam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%)
 create mode 100644 src/backend/access/sequence/seqlocalam.c
 create mode 100644 src/backend/access/sequence/seqlocalxlog.c

diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 8e7fc9db8778..b942b25350bc 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/seqlocalam.h b/src/include/access/seqlocalam.h
new file mode 100644
index 000000000000..07eeb3af8713
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2025, 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 "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_SEQ_LOCAL_LOG			0x00
+
+typedef struct xl_seq_local_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_seq_local_rec;
+
+/*
+ * 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;
+
+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);
+
+/* 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/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 96c98e800c22..6b3b3e1e44c1 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 0d289d77fcf7..6ffbcb2c4735 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.c
  *
  * Portions Copyright (c) 1996-2025, 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.h"
+#include "access/seqlocalam.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..a15ceec1c0a0 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 ec9ab9b7e9db..8bc0e95e68c0 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2025, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'seqlocalam.c',
+  'seqlocalxlog.c',
   'sequence.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
new file mode 100644
index 000000000000..a8cf83643394
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -0,0 +1,640 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/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"
+
+
+/*
+ * 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
+
+/* 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 "heap";
+}
+
+/*
+ * 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/sequence/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c
new file mode 100644
index 000000000000..ba29c41fb2f4
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalxlog.c
@@ -0,0 +1,81 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalxlog.c
+ *	  WAL replay logic for 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/seqlocalxlog.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/seqlocalam.h"
+#include "access/xlogutils.h"
+#include "storage/block.h"
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+seq_local_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+seq_local_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record);
+	seq_local_magic *sm;
+
+	if (info != XLOG_SEQ_LOCAL_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	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_local_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec);
+
+	if (PageAddItem(localpage, item, itemsz, FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "seq_local_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 1b7499726eb0..cc92268937b1 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0c6d267218ee..2b60f533b206 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -51,23 +52,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -97,13 +81,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,
@@ -135,14 +115,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
@@ -175,35 +149,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;
@@ -216,35 +161,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	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)
@@ -293,10 +223,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;
@@ -307,7 +233,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))
@@ -316,40 +241,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 */
@@ -358,105 +251,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
  *
@@ -468,10 +262,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;
@@ -480,7 +271,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,
@@ -507,16 +298,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,
@@ -526,32 +309,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 */
@@ -580,8 +341,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
@@ -596,10 +355,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);
 }
@@ -662,24 +418,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);
@@ -724,105 +471,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;
@@ -832,69 +483,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;
@@ -984,9 +572,6 @@ do_setval(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,
@@ -1020,9 +605,6 @@ do_setval(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),
@@ -1044,37 +626,8 @@ do_setval(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);
 }
@@ -1215,62 +768,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
@@ -1857,19 +1354,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));
@@ -1896,6 +1390,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);
@@ -1913,17 +1408,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)
@@ -1933,55 +1420,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 }
 
 
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, item, itemsz, FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1996,14 +1434,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c767e..8d1195de2637 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/seqlocaldesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index fac509ed134e..2fcf9fc4392a 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index f26d75e01cfd..2495a28d26a1 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

v23-0003-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From 3b56267b6489be11043308302505d61983746400 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 11:40:10 +0900
Subject: [PATCH v23 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               |  15 --
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/seqlocalam.c      |  41 +++-
 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               |  24 +--
 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, 697 insertions(+), 184 deletions(-)
 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
index 07eeb3af8713..5a0d77e392f4 100644
--- a/src/include/access/seqlocalam.h
+++ b/src/include/access/seqlocalam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_SEQ_LOCAL_LOG			0x00
@@ -41,18 +40,4 @@ 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);
 
-/* 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..1cfc28ca62e1
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,184 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2025, 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 26d15928a155..8f076fcec958 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 6e98a0930c27..080bea5031a9 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 07d182da796a..012a2863c037 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 9121a382f76b..d2e15a699fc5 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',
@@ -7914,6 +7920,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 cb730aeac864..d0054c5c988c 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 dd22b5efdfd9..6790728aced3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -145,6 +145,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 9ac0b67683d3..7693e9941fc9 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
 extern void ResetSequence(Oid seq_relid);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index ea36cb0fda40..c0311fbe8220 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 15e13b05230b..9a1f286a12ed 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3227,6 +3227,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 82ac8646a8d4..ab5e2eab0401 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 80286076a111..ee6c0559deec 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 a15ceec1c0a0..62006165a15f 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = seqlocalam.o seqlocalxlog.o sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 8bc0e95e68c0..d82af34d538c 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -4,4 +4,5 @@ backend_sources += files(
   'seqlocalam.c',
   'seqlocalxlog.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
index a8cf83643394..8c9ad974a92e 100644
--- a/src/backend/access/sequence/seqlocalam.c
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -18,6 +18,7 @@
 #include "access/htup_details.h"
 #include "access/multixact.h"
 #include "access/seqlocalam.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -25,6 +26,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -230,10 +232,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;
@@ -417,7 +419,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 "heap";
@@ -432,7 +434,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];
@@ -499,7 +501,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;
@@ -547,7 +549,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;
@@ -600,7 +602,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,
@@ -627,7 +629,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;
@@ -638,3 +640,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 8b5303553702..f30317d1feae 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..c3b43507f9af
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,146 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2025, 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 fd6537567ea2..e774dfa86e4e 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 58ed9d216cc0..e44633d13b60 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 2b60f533b206..0cecb56274a0 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -153,6 +153,7 @@ 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;
@@ -174,7 +175,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)
@@ -242,7 +243,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 */
@@ -299,7 +300,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,
@@ -312,7 +313,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 */
@@ -355,7 +356,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);
 }
@@ -472,8 +473,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;
@@ -627,7 +628,7 @@ do_setval(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);
 }
@@ -1358,8 +1359,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);
@@ -1408,7 +1408,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 b68e3d161b86..3a37e4859b35 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"
@@ -1025,14 +1026,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)
 	{
@@ -1045,6 +1050,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 9ecddb142314..0c79454f641e 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 a4b29c822e8f..927d3e786c56 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -387,6 +387,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
@@ -4989,23 +4990,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;
@@ -5042,6 +5046,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); }
 		;
@@ -6045,6 +6054,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 e96b38a59d50..7678e6e2880f 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"
@@ -518,6 +519,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 317a1f2b282f..68f160dda7d3 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 915d0bc90842..603e43d73211 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);
 
@@ -1831,17 +1835,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.
@@ -1872,6 +1868,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
  *
@@ -3711,14 +3750,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
@@ -4343,13 +4385,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);
@@ -6425,8 +6475,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);
@@ -6438,6 +6490,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 d6fc83338505..b732de094629 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2736,6 +2736,14 @@
   check_hook => 'check_default_table_access_method',
 },
 
+{ 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_tablespace', type => 'string', context => 'PGC_USERSET', group => 'CLIENT_CONN_STATEMENT',
   short_desc => 'Sets the default tablespace to create tables and indexes in.',
   long_desc => 'An empty string means use the database\'s default tablespace.',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 00c8376cf4de..905db5630ec4 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 f62b61967ef6..1a3117c2356b 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -758,6 +758,7 @@ autovacuum_worker_slots = 16	# autovacuum worker slots to allocate
 					#   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 36f245028429..d9b664dcd09e 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 36ea6a4d5570..ac5db36ba81c 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2608,7 +2608,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");
@@ -3369,7 +3369,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");
@@ -3667,7 +3667,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 a357e1d0c0e1..c2efb121f2d2 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1943,6 +1943,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 fa8984ffe0da..12ea003528c2 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5129,31 +5129,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
@@ -5178,32 +5180,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 943e56506bf1..f36e267c4b98 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 df795759bb4c..dbaceb9fd2c8 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 df88c78fe3a4..e7b44dd0dbad 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2701,6 +2701,7 @@ SeqScanState
 SeqTable
 SeqTableData
 SeqType
+SequenceAmRoutine
 SequenceItem
 SerCommitSeqNo
 SerialControl
@@ -3770,6 +3771,7 @@ list_sort_comparator
 loc_chunk
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 local_ts_iter
 local_ts_radix_tree
@@ -4056,7 +4058,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
@@ -4283,6 +4284,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
@@ -4294,7 +4296,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

v23-0004-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From 52360bef5f5046d485d8644c99deaaa384916efd Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 15 Aug 2025 14:35:50 +0900
Subject: [PATCH v23 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 59eaecb4ed71..39420da1661e 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;
@@ -2420,6 +2422,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 */
 
@@ -2687,6 +2690,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);
@@ -2791,6 +2795,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);
 
@@ -3536,6 +3543,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;
 
@@ -3698,6 +3708,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.
  */
@@ -3907,6 +3968,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);
 
@@ -4427,6 +4489,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);
@@ -5166,6 +5230,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -5225,6 +5290,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 47913178a93b..9b7e7905f131 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},
@@ -1249,6 +1251,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;
@@ -1371,6 +1374,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 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"));
@@ -14419,6 +14423,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;
@@ -18902,26 +18909,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);
@@ -18941,6 +18962,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);
@@ -19012,6 +19037,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);
 	}
@@ -19134,6 +19160,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 bb451c1bae14..2d52d96514d6 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},
@@ -450,6 +452,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)
@@ -731,6 +735,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 c9776306c5c4..b2e25da92b92 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},
@@ -435,6 +437,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;
@@ -566,6 +569,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 445a541abf63..360b221d56d6 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 fd4ecf01a0a0..895e324ba193 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 9f639f61db02..94a5bad771b2 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

v23-0005-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From bf2b8e46938aa06a66053c577685f6109f79d588 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v23 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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 0a2a8b49fdba..da8c916ed431 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9826,6 +9826,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 af476c82fcc1..9f369cc89930 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..3067dc4d4df0 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>SEQUENCE</literal> and
+      <literal>INDEX</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 1e283f13d15c..52c6096e4ba2 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..a96170bfac03
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.51.0

v23-0006-Refactor-logic-for-page-manipulations-of-sequenc.patchtext/x-diff; charset=us-asciiDownload
From 2e5573203af88d533905cd17c7b9ac6b4cac9f0f Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 11:29:39 +0900
Subject: [PATCH v23 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 8c9ad974a92e..d7868f4fcbb0 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/seqlocalam.h"
 #include "access/sequenceam.h"
+#include "access/sequence_page.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -83,27 +84,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
@@ -122,8 +107,6 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 		MarkBufferDirtyHint(*buf, true);
 	}
 
-	seq = (Form_pg_seq_local_data) GETSTRUCT(seqdatatuple);
-
 	return seq;
 }
 
@@ -162,33 +145,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

v23-0007-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From b623c57b702706d5aab57663fa4c247a85b455c9 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 30 Oct 2025 16:32:47 +0900
Subject: [PATCH v23 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 ed30ee7d639f..e3bd2b464172 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..bb0270529852
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2025, 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 += bloom
+
+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..91eb7a8995c4
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,519 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2025, 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

#48Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#47)
7 attachment(s)
Re: Sequence Access Methods, round two

On Thu, Oct 30, 2025 at 04:44:23PM +0900, Michael Paquier wrote:

Rebased due to conflicts with 76acf4b722fa.

Rebased as v24 as GUCs need to be in alphabetical order.
--
Michael

Attachments:

v24-0005-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From 6baed5324a9db9521a14befe36fbc70a0a190fc5 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v24 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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 d8a9f14b618f..176ca62176f0 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9826,6 +9826,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 af476c82fcc1..9f369cc89930 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..3067dc4d4df0 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>SEQUENCE</literal> and
+      <literal>INDEX</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 1e283f13d15c..52c6096e4ba2 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..a96170bfac03
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.51.0

v24-0001-Integrate-addition-of-attributes-for-sequences-w.patchtext/x-diff; charset=us-asciiDownload
From de2db58cac88bafb40f9834cc89e7a762a4c9b3f Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v24 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               | 29 +++++++++++++++++--
 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, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d14294a4eceb..e95302b1a362 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2415,6 +2415,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 8d671b7a29d6..2b65269a4ef3 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -136,6 +136,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;
@@ -174,7 +177,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;
@@ -198,7 +200,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;
@@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 3aac459e483d..3e45de42e3bb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4648,6 +4648,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 */
@@ -4943,6 +4944,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,
@@ -5375,6 +5383,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,
@@ -6590,6 +6599,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 082967c0a86d..8bff082aeac8 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1666,7 +1666,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 50d0354a3417..ed31059ef584 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
@@ -70,7 +73,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 193669f2bc1e..254fdf90c79a 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

v24-0002-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From 8e67cc841bf013a574c649326b7a28808c97683d Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 09:18:40 +0900
Subject: [PATCH v24 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.
- seqlocalxlog.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/seqlocalam.h               |  58 ++
 src/backend/access/rmgrdesc/Makefile          |   2 +-
 src/backend/access/rmgrdesc/meson.build       |   2 +-
 .../rmgrdesc/{seqdesc.c => seqlocaldesc.c}    |  20 +-
 src/backend/access/sequence/Makefile          |   2 +-
 src/backend/access/sequence/meson.build       |   2 +
 src/backend/access/sequence/seqlocalam.c      | 640 ++++++++++++++++++
 src/backend/access/sequence/seqlocalxlog.c    |  81 +++
 src/backend/access/transam/rmgr.c             |   1 +
 src/backend/commands/sequence.c               | 639 +----------------
 src/bin/pg_waldump/.gitignore                 |   2 +-
 src/bin/pg_waldump/rmgrdesc.c                 |   1 +
 src/bin/pg_waldump/t/001_basic.pl             |   2 +-
 14 files changed, 832 insertions(+), 622 deletions(-)
 create mode 100644 src/include/access/seqlocalam.h
 rename src/backend/access/rmgrdesc/{seqdesc.c => seqlocaldesc.c} (64%)
 create mode 100644 src/backend/access/sequence/seqlocalam.c
 create mode 100644 src/backend/access/sequence/seqlocalxlog.c

diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 8e7fc9db8778..b942b25350bc 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/seqlocalam.h b/src/include/access/seqlocalam.h
new file mode 100644
index 000000000000..07eeb3af8713
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2025, 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 "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+#include "utils/rel.h"
+
+/* XLOG stuff */
+#define XLOG_SEQ_LOCAL_LOG			0x00
+
+typedef struct xl_seq_local_rec
+{
+	RelFileLocator locator;
+	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_seq_local_rec;
+
+/*
+ * 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;
+
+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);
+
+/* 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/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 96c98e800c22..6b3b3e1e44c1 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 0d289d77fcf7..6ffbcb2c4735 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.c
  *
  * Portions Copyright (c) 1996-2025, 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.h"
+#include "access/seqlocalam.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..a15ceec1c0a0 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 ec9ab9b7e9db..8bc0e95e68c0 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2025, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'seqlocalam.c',
+  'seqlocalxlog.c',
   'sequence.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
new file mode 100644
index 000000000000..a8cf83643394
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -0,0 +1,640 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/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"
+
+
+/*
+ * 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
+
+/* 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 "heap";
+}
+
+/*
+ * 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/sequence/seqlocalxlog.c b/src/backend/access/sequence/seqlocalxlog.c
new file mode 100644
index 000000000000..ba29c41fb2f4
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalxlog.c
@@ -0,0 +1,81 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalxlog.c
+ *	  WAL replay logic for 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/seqlocalxlog.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/seqlocalam.h"
+#include "access/xlogutils.h"
+#include "storage/block.h"
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+seq_local_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
+
+void
+seq_local_redo(XLogReaderState *record)
+{
+	XLogRecPtr	lsn = record->EndRecPtr;
+	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+	Buffer		buffer;
+	Page		page;
+	Page		localpage;
+	char	   *item;
+	Size		itemsz;
+	xl_seq_local_rec *xlrec = (xl_seq_local_rec *) XLogRecGetData(record);
+	seq_local_magic *sm;
+
+	if (info != XLOG_SEQ_LOCAL_LOG)
+		elog(PANIC, "seq_redo: unknown op code %u", info);
+
+	buffer = XLogInitBufferForRedo(record, 0);
+	page = BufferGetPage(buffer);
+
+	/*
+	 * We always reinit the page.  However, since this WAL record type is also
+	 * used for updating sequences, it's possible that a hot-standby backend
+	 * is examining the page concurrently; so we mustn't transiently trash the
+	 * buffer.  The solution is to build the correct new page contents in
+	 * local workspace and then memcpy into the buffer.  Then only bytes that
+	 * are supposed to change will change, even transiently. We must palloc
+	 * the local page for alignment reasons.
+	 */
+	localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+	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_local_rec);
+	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_local_rec);
+
+	if (PageAddItem(localpage, item, itemsz, FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+		elog(PANIC, "seq_local_redo: failed to add item to page");
+
+	PageSetLSN(localpage, lsn);
+
+	memcpy(page, localpage, BufferGetPageSize(buffer));
+	MarkBufferDirty(buffer);
+	UnlockReleaseBuffer(buffer);
+
+	pfree(localpage);
+}
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 1b7499726eb0..cc92268937b1 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,6 +27,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/spgxlog.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 2b65269a4ef3..262c03e13ced 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,6 +16,7 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
@@ -51,23 +52,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
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC	  0x1717
-
-typedef struct sequence_magic
-{
-	uint32		magic;
-} sequence_magic;
-
 /*
  * 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
@@ -97,13 +81,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,
@@ -134,14 +114,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
@@ -174,35 +148,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;
@@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	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)
@@ -292,10 +222,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;
@@ -306,7 +232,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))
@@ -315,40 +240,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 */
@@ -357,105 +250,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
  *
@@ -467,10 +261,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;
@@ -479,7 +270,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,
@@ -506,16 +297,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,
@@ -525,32 +308,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 */
@@ -579,8 +340,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
@@ -595,10 +354,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);
 }
@@ -661,24 +417,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);
@@ -723,105 +470,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;
@@ -831,69 +482,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;
@@ -983,9 +571,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,
@@ -1019,9 +604,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),
@@ -1043,37 +625,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);
 }
@@ -1214,62 +767,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
@@ -1857,19 +1354,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));
@@ -1896,6 +1390,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);
@@ -1913,17 +1408,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)
@@ -1933,55 +1420,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
 }
 
 
-void
-seq_redo(XLogReaderState *record)
-{
-	XLogRecPtr	lsn = record->EndRecPtr;
-	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
-	Buffer		buffer;
-	Page		page;
-	Page		localpage;
-	char	   *item;
-	Size		itemsz;
-	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
-	sequence_magic *sm;
-
-	if (info != XLOG_SEQ_LOG)
-		elog(PANIC, "seq_redo: unknown op code %u", info);
-
-	buffer = XLogInitBufferForRedo(record, 0);
-	page = BufferGetPage(buffer);
-
-	/*
-	 * We always reinit the page.  However, since this WAL record type is also
-	 * used for updating sequences, it's possible that a hot-standby backend
-	 * is examining the page concurrently; so we mustn't transiently trash the
-	 * buffer.  The solution is to build the correct new page contents in
-	 * local workspace and then memcpy into the buffer.  Then only bytes that
-	 * are supposed to change will change, even transiently. We must palloc
-	 * the local page for alignment reasons.
-	 */
-	localpage = (Page) palloc(BufferGetPageSize(buffer));
-
-	PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
-	sm = (sequence_magic *) PageGetSpecialPointer(localpage);
-	sm->magic = SEQ_MAGIC;
-
-	item = (char *) xlrec + sizeof(xl_seq_rec);
-	itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
-	if (PageAddItem(localpage, item, itemsz, FirstOffsetNumber, false, false) == InvalidOffsetNumber)
-		elog(PANIC, "seq_redo: failed to add item to page");
-
-	PageSetLSN(localpage, lsn);
-
-	memcpy(page, localpage, BufferGetPageSize(buffer));
-	MarkBufferDirty(buffer);
-	UnlockReleaseBuffer(buffer);
-
-	pfree(localpage);
-}
-
 /*
  * Flush cached sequence information.
  */
@@ -1996,14 +1434,3 @@ ResetSequenceCaches(void)
 
 	last_used_seq = NULL;
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c767e..8d1195de2637 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/seqlocaldesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.c
 /smgrdesc.c
 /spgdesc.c
 /standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index fac509ed134e..2fcf9fc4392a 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index f26d75e01cfd..2495a28d26a1 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

v24-0003-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From 89b280509605e22817adc6eed2837c5410a1d832 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 7 Nov 2025 15:07:58 +0900
Subject: [PATCH v24 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               |  15 --
 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               |  34 ----
 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          |   2 +-
 src/backend/access/sequence/meson.build       |   1 +
 src/backend/access/sequence/seqlocalam.c      |  41 +++-
 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               |  24 +--
 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, 697 insertions(+), 184 deletions(-)
 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
index 07eeb3af8713..5a0d77e392f4 100644
--- a/src/include/access/seqlocalam.h
+++ b/src/include/access/seqlocalam.h
@@ -15,7 +15,6 @@
 
 #include "access/xlogreader.h"
 #include "storage/relfilelocator.h"
-#include "utils/rel.h"
 
 /* XLOG stuff */
 #define XLOG_SEQ_LOCAL_LOG			0x00
@@ -41,18 +40,4 @@ 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);
 
-/* 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..1cfc28ca62e1
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,184 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2025, 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 26d15928a155..8f076fcec958 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 6e98a0930c27..080bea5031a9 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 07d182da796a..012a2863c037 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 34b7fddb0e7a..38776b98c6da 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',
@@ -7914,6 +7920,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 cb730aeac864..d0054c5c988c 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 dd22b5efdfd9..6790728aced3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -145,6 +145,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 46b4d89dd6ea..fa47d8dd3dd5 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
 #include "storage/relfilelocator.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
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG			0x00
-
-typedef struct xl_seq_rec
-{
-	RelFileLocator locator;
-	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
 extern int64 nextval_internal(Oid relid, bool check_permissions);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
@@ -63,9 +34,4 @@ extern void ResetSequence(Oid seq_relid);
 extern void SetSequence(Oid relid, int64 next, bool is_called);
 extern void ResetSequenceCaches(void);
 
-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_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index ea36cb0fda40..c0311fbe8220 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 e95302b1a362..f09f102ee976 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3227,6 +3227,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 82ac8646a8d4..ab5e2eab0401 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 80286076a111..ee6c0559deec 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 a15ceec1c0a0..62006165a15f 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = seqlocalam.o seqlocalxlog.o sequence.o
+OBJS = seqlocalam.o seqlocalxlog.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 8bc0e95e68c0..d82af34d538c 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -4,4 +4,5 @@ backend_sources += files(
   'seqlocalam.c',
   'seqlocalxlog.c',
   'sequence.c',
+  'sequenceamapi.c',
 )
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
index a8cf83643394..8c9ad974a92e 100644
--- a/src/backend/access/sequence/seqlocalam.c
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -18,6 +18,7 @@
 #include "access/htup_details.h"
 #include "access/multixact.h"
 #include "access/seqlocalam.h"
+#include "access/sequenceam.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -25,6 +26,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -230,10 +232,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;
@@ -417,7 +419,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 "heap";
@@ -432,7 +434,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];
@@ -499,7 +501,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;
@@ -547,7 +549,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;
@@ -600,7 +602,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,
@@ -627,7 +629,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;
@@ -638,3 +640,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 8b5303553702..f30317d1feae 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..c3b43507f9af
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,146 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2025, 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 fd6537567ea2..e774dfa86e4e 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 58ed9d216cc0..e44633d13b60 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 262c03e13ced..3afec2999375 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -16,10 +16,10 @@
 
 #include "access/bufmask.h"
 #include "access/htup_details.h"
-#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -152,6 +152,7 @@ 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;
@@ -173,7 +174,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)
@@ -241,7 +242,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 */
@@ -298,7 +299,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,
@@ -311,7 +312,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 */
@@ -354,7 +355,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);
 }
@@ -471,8 +472,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;
@@ -626,7 +627,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);
 }
@@ -1358,8 +1359,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);
@@ -1408,7 +1408,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 3e45de42e3bb..a484065d8e9a 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"
@@ -1025,14 +1026,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)
 	{
@@ -1045,6 +1050,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 9ecddb142314..0c79454f641e 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 57fe01865470..3e4f634171f5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -388,6 +388,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
@@ -5010,23 +5011,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;
@@ -5063,6 +5067,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); }
 		;
@@ -6066,6 +6075,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 e96b38a59d50..7678e6e2880f 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"
@@ -518,6 +519,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 317a1f2b282f..68f160dda7d3 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 915d0bc90842..603e43d73211 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);
 
@@ -1831,17 +1835,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.
@@ -1872,6 +1868,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
  *
@@ -3711,14 +3750,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
@@ -4343,13 +4385,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);
@@ -6425,8 +6475,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);
@@ -6438,6 +6490,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 1128167c0251..2b3f7536be41 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -691,6 +691,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 0209b2067a22..de7cd5ca5c98 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 f62b61967ef6..1a3117c2356b 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -758,6 +758,7 @@ autovacuum_worker_slots = 16	# autovacuum worker slots to allocate
 					#   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 36f245028429..d9b664dcd09e 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 316a2dafbf1e..cb4c5319cb2e 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2619,7 +2619,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");
@@ -3399,7 +3399,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");
@@ -3697,7 +3697,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 a357e1d0c0e1..c2efb121f2d2 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1943,6 +1943,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 943e56506bf1..f36e267c4b98 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 df795759bb4c..dbaceb9fd2c8 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 432509277c98..0d6526c2de0e 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2704,6 +2704,7 @@ SeqScanState
 SeqTable
 SeqTableData
 SeqType
+SequenceAmRoutine
 SequenceItem
 SerCommitSeqNo
 SerialControl
@@ -3778,6 +3779,7 @@ list_sort_comparator
 loc_chunk
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 local_ts_iter
 local_ts_radix_tree
@@ -4064,7 +4066,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
@@ -4291,6 +4292,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
@@ -4302,7 +4304,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

v24-0004-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From ff9b8c068b78054617d236ada2dd75672f05c4a4 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 15 Aug 2025 14:35:50 +0900
Subject: [PATCH v24 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 59eaecb4ed71..39420da1661e 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;
@@ -2420,6 +2422,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 */
 
@@ -2687,6 +2690,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);
@@ -2791,6 +2795,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);
 
@@ -3536,6 +3543,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;
 
@@ -3698,6 +3708,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.
  */
@@ -3907,6 +3968,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);
 
@@ -4427,6 +4489,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);
@@ -5166,6 +5230,7 @@ CloneArchive(ArchiveHandle *AH)
 	clone->connCancel = NULL;
 	clone->currUser = NULL;
 	clone->currSchema = NULL;
+	clone->currSequenceAm = NULL;
 	clone->currTableAm = NULL;
 	clone->currTablespace = NULL;
 
@@ -5225,6 +5290,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 a00918bacb40..3833b9401f0d 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},
@@ -1249,6 +1251,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;
@@ -1371,6 +1374,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 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"));
@@ -14423,6 +14427,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;
@@ -18906,26 +18913,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);
@@ -18945,6 +18966,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);
@@ -19016,6 +19041,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);
 	}
@@ -19138,6 +19164,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 bb451c1bae14..2d52d96514d6 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},
@@ -450,6 +452,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)
@@ -731,6 +735,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 c9776306c5c4..b2e25da92b92 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},
@@ -435,6 +437,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;
@@ -566,6 +569,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 445a541abf63..360b221d56d6 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 fd4ecf01a0a0..895e324ba193 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 9f639f61db02..94a5bad771b2 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

v24-0006-Refactor-logic-for-page-manipulations-of-sequenc.patchtext/x-diff; charset=us-asciiDownload
From 160a9f0b382c2b72c9bc3696c6b864f4c7064a17 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 11:29:39 +0900
Subject: [PATCH v24 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 8c9ad974a92e..d7868f4fcbb0 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/seqlocalam.h"
 #include "access/sequenceam.h"
+#include "access/sequence_page.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -83,27 +84,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
@@ -122,8 +107,6 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 		MarkBufferDirtyHint(*buf, true);
 	}
 
-	seq = (Form_pg_seq_local_data) GETSTRUCT(seqdatatuple);
-
 	return seq;
 }
 
@@ -162,33 +145,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

v24-0007-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From a8db48c12d663ac38a4626657701d09f201109a1 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 30 Oct 2025 16:32:47 +0900
Subject: [PATCH v24 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 ed30ee7d639f..e3bd2b464172 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..bb0270529852
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2025, 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 += bloom
+
+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..91eb7a8995c4
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,519 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2025, 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

#49Andrei Lepikhov
lepihov@gmail.com
In reply to: Michael Paquier (#48)
Re: Sequence Access Methods, round two

On 7/11/2025 07:12, Michael Paquier wrote:

On Thu, Oct 30, 2025 at 04:44:23PM +0900, Michael Paquier wrote:

Rebased due to conflicts with 76acf4b722fa.

Rebased as v24 as GUCs need to be in alphabetical order.

Thanks for your efforts!
I haven't dived into the code yet; I just want to add some words about
the reasoning.
1. For pgEdge Snowflake, it would be really beneficial - there are not
only code copying issues, but also the Serial -> Snowflake conversion
algorithm. Additionally, for now, only an in-core sequence can be used
as an 'identity always' column.
2. Multimaster project [1]https://github.com/postgrespro/mmts also attempts to resolve the sequence issue.
You may check how its developers tackle the problem - I guess any
active-active configuration needs it.
3. I wonder if postgres_fdw-based shardman could benefit from that. I
imagine an implementation of an auto-partitioned table where the
partitioning key is generated automatically based on the partition where
the tuple is inserted.
4. Any kind of non-uniform distribution may be implemented that is
helpful for testing purposes

[1]: https://github.com/postgrespro/mmts

--
regards, Andrei Lepikhov,
pgEdge

#50Michael Paquier
michael@paquier.xyz
In reply to: Andrei Lepikhov (#49)
Re: Sequence Access Methods, round two

On Fri, Nov 07, 2025 at 05:05:55PM +0100, Andrei Lepikhov wrote:

1. For pgEdge Snowflake, it would be really beneficial - there are not only
code copying issues, but also the Serial -> Snowflake conversion algorithm.
Additionally, for now, only an in-core sequence can be used as an 'identity
always' column.

Perhaps pgedge is bypassing that by telling users to enforce a
sequence rule with a function attached to an attribute default?
Schema modifications usually feel meh for any users.

2. Multimaster project [1] also attempts to resolve the sequence issue. You
may check how its developers tackle the problem - I guess any active-active
configuration needs it.

I've wanted that for 15 years or so with for strictly-monotone values
shared across nodes, so there's that. Remember Postgres-XC.

3. I wonder if postgres_fdw-based shardman could benefit from that. I
imagine an implementation of an auto-partitioned table where the
partitioning key is generated automatically based on the partition where the
tuple is inserted.

Yeah, I don't see why this would not work.

4. Any kind of non-uniform distribution may be implemented that is helpful
for testing purposes

This one is perhaps nice to have but it's low priority compared to the
others and the possibility to hide the computation behind native DDLs
that use sequences underground (SERIAL and GENERATED).
--
Michael

#51Andrei Lepikhov
lepihov@gmail.com
In reply to: Michael Paquier (#50)
Re: Sequence Access Methods, round two

On 8/11/2025 00:02, Michael Paquier wrote:

On Fri, Nov 07, 2025 at 05:05:55PM +0100, Andrei Lepikhov wrote:

1. For pgEdge Snowflake, it would be really beneficial - there are not only
code copying issues, but also the Serial -> Snowflake conversion algorithm.
Additionally, for now, only an in-core sequence can be used as an 'identity
always' column.

Perhaps pgedge is bypassing that by telling users to enforce a
sequence rule with a function attached to an attribute default?
Schema modifications usually feel meh for any users.

Like that.

2. Multimaster project [1] also attempts to resolve the sequence issue. You
may check how its developers tackle the problem - I guess any active-active
configuration needs it.

I've wanted that for 15 years or so with for strictly-monotone values
shared across nodes, so there's that. Remember Postgres-XC.

In multimaster, you may find an implementation of strictly monotone
sequences. This method is covered by a GUC, impolitely named as
'Volkswagen method'. However, it adds a massive overhead and is designed
only for very narrow cases.

I skimmed your patches. It seems that 0003 and 0007 should be the first
patches in the set.
Additionally, the approach itself appears overly complicated to me.
Generally, I need a kinda of type parameter (like typmod) where I can
propose the OID of the nextval routine and extra parameters - it may be
serialised somewhere in pg_attribute.
It seems even more flexible than a default_sequence_access_method,
doesn't it?

--
regards, Andrei Lepikhov,
pgEdge

#52Michael Paquier
michael@paquier.xyz
In reply to: Andrei Lepikhov (#51)
Re: Sequence Access Methods, round two

On Mon, Nov 10, 2025 at 04:31:07PM +0100, Andrei Lepikhov wrote:

In multimaster, you may find an implementation of strictly monotone
sequences. This method is covered by a GUC, impolitely named as 'Volkswagen
method'.

I'm afraid that I cannot understand what you mean with this reference.

However, it adds a massive overhead and is designed only for very
narrow cases.

Are you referring to the function redirections required for the
callbacks here? Or are you referring to something more specific?

I skimmed your patches. It seems that 0003 and 0007 should be the first
patches in the set.

Patch 0003 is named "Sequence access methods - backend support",
relying on two refactoring pieces. Patch 0007 is the snowflake
contrib module, which is based on all the other patches. So I don't
quite see what would be the point here based on how the patch is
currently written?

Additionally, the approach itself appears overly complicated to me.
Generally, I need a kinda of type parameter (like typmod) where I can
propose the OID of the nextval routine and extra parameters - it may be
serialised somewhere in pg_attribute.
It seems even more flexible than a default_sequence_access_method, doesn't
it?

Hmm. How would this fit into a DDL model? The advantage of the GUC
is to be able to hide from the client which sequence computation
method is used internally, with something like the following to forget
about the knowledge of the computation, so as it's fully transparent:
CREATE ACCESS METHOD popo
TYPE SEQUENCE HANDLER popo_sequenceam_handler;
SET default_sequence_access_method = popo;
CREATE TABLE tab (a int GENERATED ALWAYS AS IDENTITY, b int);

It seems to me that you are proposing something slightly different,
then with an extra option, say a sort of "COMPUTATION" clause for
CREATE TABLE's sequence_options or CREATE SEQUENCE, where the name of
the function to use would be given to bypass nextval(). I don't
really get how this solves the transparency argument, because we'd
still need to update all the schemas to use something a different
function for the nextval(), no? It would not deal with a bunch of
other states, like how we fetch sequence meta-data on upgrades, for
the new logical decoding feature where we may want a page LSN, and
setval(). lastval() remains the easy one as we just need to cache the
last 8-byte value, and we maintain the name of the sequence in a
static cache for each backend.

Anyway, there are also two more properties related to the in-core
sequences where the cases I have seen do not want to deal with:
- WAL generation, currently tracked by log_cnt, or be able to bypass
entirely WAL, or control the rate of the records.
- Storage, where no local storage is required for sequences.

It sounds like your case does really care much about these dealing
with these, where you would only need a shortcut to pass a nextval()
for a sequence, but I cannot say for sure if these two properties
matter for you, as well.
--
Michael

#53Andrei Lepikhov
lepihov@gmail.com
In reply to: Michael Paquier (#52)
Re: Sequence Access Methods, round two

On 11/11/2025 02:43, Michael Paquier wrote:

On Mon, Nov 10, 2025 at 04:31:07PM +0100, Andrei Lepikhov wrote:

In multimaster, you may find an implementation of strictly monotone
sequences. This method is covered by a GUC, impolitely named as 'Volkswagen
method'.

I'm afraid that I cannot understand what you mean with this reference.

I mean sequence values generation mode, regulated by the GUC [1]https://github.com/postgrespro/mmts/blob/baab9238f784d428481ecfa1294e3f9a3910b2d2/src/multimaster.c#L578.

However, it adds a massive overhead and is designed only for very
narrow cases.

Are you referring to the function redirections required for the
callbacks here? Or are you referring to something more specific?

Yes, it is more specific - strictly growing cross-node sequence where we
need to acquire a global lock. I have written about it by accident -
just to demonstrate that some Postgres-inherited implementations exist
and are used in production.

I skimmed your patches. It seems that 0003 and 0007 should be the first
patches in the set.

Patch 0003 is named "Sequence access methods - backend support",
relying on two refactoring pieces. Patch 0007 is the snowflake
contrib module, which is based on all the other patches. So I don't
quite see what would be the point here based on how the patch is
currently written?

Hmm. In v24, 0003 introduces AM, 0007 - an example of how to use it. Ok,
maybe I need to dive into the code a little more.
The 0001 and 0002 introduce additional attributes to sequences and code
refactoring, which were partly rewritten by subsequent patches. It seems
to me that 0001 and 0002 might be reviewed and committed after the main
code.

Additionally, the approach itself appears overly complicated to me.
Generally, I need a kinda of type parameter (like typmod) where I can
propose the OID of the nextval routine and extra parameters - it may be
serialised somewhere in pg_attribute.
It seems even more flexible than a default_sequence_access_method, doesn't
it?

Hmm. How would this fit into a DDL model? The advantage of the GUC
is to be able to hide from the client which sequence computation
method is used internally, with something like the following to forget
about the knowledge of the computation, so as it's fully transparent:
CREATE ACCESS METHOD popo
TYPE SEQUENCE HANDLER popo_sequenceam_handler;
SET default_sequence_access_method = popo;
CREATE TABLE tab (a int GENERATED ALWAYS AS IDENTITY, b int);

Yes, I have no idea how to implement my imaginary example. I just wanted
to say that the implementation looks massive and isn't flexible enough
to let two columns of a table have different AM. Not sure if it is a
critical case, though, but I constantly keep in mind benchmarking with
sophisticated distributions.

It sounds like your case does really care much about these dealing
with these, where you would only need a shortcut to pass a nextval()
for a sequence, but I cannot say for sure if these two properties
matter for you, as well.

Absolutely. I would like to utilise sequence AM for such sort of tasks,
particularly to simplify demonstration of how the optimiser behaves when
there are changes in the data distribution law. However, it does seem
quite specific.

[1]: https://github.com/postgrespro/mmts/blob/baab9238f784d428481ecfa1294e3f9a3910b2d2/src/multimaster.c#L578
https://github.com/postgrespro/mmts/blob/baab9238f784d428481ecfa1294e3f9a3910b2d2/src/multimaster.c#L578

--
regards, Andrei Lepikhov,
pgEdge

#54Michael Paquier
michael@paquier.xyz
In reply to: Andrei Lepikhov (#53)
Re: Sequence Access Methods, round two

On Tue, Nov 11, 2025 at 09:59:40AM +0100, Andrei Lepikhov wrote:

On 11/11/2025 02:43, Michael Paquier wrote:

On Mon, Nov 10, 2025 at 04:31:07PM +0100, Andrei Lepikhov wrote:

In multimaster, you may find an implementation of strictly monotone
sequences. This method is covered by a GUC, impolitely named as 'Volkswagen
method'.

I'm afraid that I cannot understand what you mean with this reference.

I mean sequence values generation mode, regulated by the GUC [1].

This is an entirely different product and project.

Hmm. In v24, 0003 introduces AM, 0007 - an example of how to use it. Ok,
maybe I need to dive into the code a little more.
The 0001 and 0002 introduce additional attributes to sequences and code
refactoring, which were partly rewritten by subsequent patches. It seems to
me that 0001 and 0002 might be reviewed and committed after the main code.

Err, no. It's the opposite here: code cleanups and file splits are
cleaner if they happen first, not after the implementations as these
lead to less code churn overall. Splitting the sequence "core" logic
and WAL logic make sense in the long-term to me anyway, as a separate
refactoring piece. It's true that 0002 could be slightly different,
though, we could for example keep sequence.c where it is now in
src/backend/commands/ without forcing the use of the term "AM" in the
file names, and extract the WAL pieces of it into a new file (aka the
redo and marking routines). Then it's only a game of moving the files
around depending on the follow-up pieces. I should probably post a
patch for that separately, this has been bugging me a bit in terms of
code separation clarity for the sequence RMGR.

Hmm. How would this fit into a DDL model? The advantage of the GUC
is to be able to hide from the client which sequence computation
method is used internally, with something like the following to forget
about the knowledge of the computation, so as it's fully transparent:
CREATE ACCESS METHOD popo
TYPE SEQUENCE HANDLER popo_sequenceam_handler;
SET default_sequence_access_method = popo;
CREATE TABLE tab (a int GENERATED ALWAYS AS IDENTITY, b int);

Yes, I have no idea how to implement my imaginary example. I just wanted to
say that the implementation looks massive and isn't flexible enough to let
two columns of a table have different AM. Not sure if it is a critical case,
though, but I constantly keep in mind benchmarking with sophisticated
distributions.

Okay.

It sounds like your case does really care much about these dealing
with these, where you would only need a shortcut to pass a nextval()
for a sequence, but I cannot say for sure if these two properties
matter for you, as well.

Absolutely. I would like to utilise sequence AM for such sort of tasks,
particularly to simplify demonstration of how the optimiser behaves when
there are changes in the data distribution law. However, it does seem quite
specific.

Hmm. Not sure how the optimizer needs to be related to the
cost computation of the optimizer. You could already mess up with
that with some of planner hooks anyway, it does not have to be related
to this patch set specifically.
--
Michael

#55Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#54)
7 attachment(s)
Re: Sequence Access Methods, round two

On Thu, Nov 13, 2025 at 08:51:32AM +0900, Michael Paquier wrote:

Err, no. It's the opposite here: code cleanups and file splits are
cleaner if they happen first, not after the implementations as these
lead to less code churn overall. Splitting the sequence "core" logic
and WAL logic make sense in the long-term to me anyway, as a separate
refactoring piece. It's true that 0002 could be slightly different,
though, we could for example keep sequence.c where it is now in
src/backend/commands/ without forcing the use of the term "AM" in the
file names, and extract the WAL pieces of it into a new file (aka the
redo and marking routines). Then it's only a game of moving the files
around depending on the follow-up pieces. I should probably post a
patch for that separately, this has been bugging me a bit in terms of
code separation clarity for the sequence RMGR.

Since this update, the code related to the sequence RMGR has been
moved around with a87987cafca6. This makes the rebased version of the
patch leaner, with a cleaner split between the WAL and "core"
computation logic, as v24 and older patch sets submitted on this
thread were splitting this code already.

Anyway. Rebased. v25. Attached.
--
Michael

Attachments:

v25-0001-Integrate-addition-of-attributes-for-sequences-w.patchtext/x-diff; charset=us-asciiDownload
From 0efb5d5ceafa68f1290192a8efb5d3ac06bc276d Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v25 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               | 29 +++++++++++++++++--
 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, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index bc7adba4a0fc..d9e868d61796 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2441,6 +2441,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 51567994126f..e7c9b66ec546 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;
@@ -197,12 +199,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 6b1a00ed477d..973f8073f858 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4653,6 +4653,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 */
@@ -4953,6 +4954,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,
@@ -5391,6 +5399,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,
@@ -6622,6 +6631,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 d18a3a60a467..9eaf16a80466 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1666,7 +1666,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 17d72e412ff8..453d02aead62 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

v25-0002-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From fd7af523c6be1a630fb95ec1d1ba88ec7e2c43d2 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 18 Dec 2025 13:26:08 +0900
Subject: [PATCH v25 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.
- seqlocalxlog.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}    |  22 +-
 src/backend/access/sequence/Makefile          |   4 +-
 src/backend/access/sequence/meson.build       |   2 +
 .../sequence/seqlocal_xlog.c}                 |  53 +-
 src/backend/access/sequence/seqlocalam.c      | 641 ++++++++++++++++++
 src/backend/access/transam/rmgr.c             |   2 +-
 src/backend/commands/Makefile                 |   1 -
 src/backend/commands/meson.build              |   1 -
 src/backend/commands/sequence.c               | 570 +---------------
 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, 801 insertions(+), 629 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} (69%)
 create mode 100644 src/backend/access/sequence/seqlocalam.c

diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 8e7fc9db8778..b942b25350bc 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..02c172135d29
--- /dev/null
+++ b/src/include/access/seqlocal_xlog.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocal_xlog.h
+ *	  Local sequence WAL definitions.
+ *
+ * Portions Copyright (c) 1996-2025, 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..50e8f0373dd8
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2025, 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 c8cf0112c382..000000000000
--- a/src/include/commands/sequence_xlog.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * sequence_xlog.h
- *	  Sequence WAL definitions.
- *
- * Portions Copyright (c) 1996-2025, 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 96c98e800c22..6b3b3e1e44c1 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 a0edb78856bd..6c0dc8c009d2 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-2025, 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 ec9ab9b7e9db..a0e0437ec063 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2025, 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 69%
rename from src/backend/commands/sequence_xlog.c
rename to src/backend/access/sequence/seqlocal_xlog.c
index ffbd9820416a..db09726fe65e 100644
--- a/src/backend/commands/sequence_xlog.c
+++ b/src/backend/access/sequence/seqlocal_xlog.c
@@ -1,26 +1,38 @@
 /*-------------------------------------------------------------------------
  *
- * sequence.c
- *	  RMGR WAL routines for sequences.
+ * seqlocalxlog.c
+ *	  WAL replay logic for 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/commands/sequence_xlog.c
+ *        src/backend/access/sequence/seqlocalxlog.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"
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+seq_local_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
 
 void
-seq_redo(XLogReaderState *record)
+seq_local_redo(XLogReaderState *record)
 {
 	XLogRecPtr	lsn = record->EndRecPtr;
 	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
@@ -29,10 +41,10 @@ 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)
+	if (info != XLOG_SEQ_LOCAL_LOG)
 		elog(PANIC, "seq_redo: unknown op code %u", info);
 
 	buffer = XLogInitBufferForRedo(record, 0);
@@ -49,15 +61,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);
 
@@ -67,14 +79,3 @@ seq_redo(XLogReaderState *record)
 
 	pfree(localpage);
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
new file mode 100644
index 000000000000..b5f3b2707f56
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -0,0 +1,641 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/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"
+
+
+/*
+ * 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
+
+/* 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 "heap";
+}
+
+/*
+ * 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..9ffb2f07530d 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,13 +27,13 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocal_xlog.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.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 5fc35826b1cc..9f640ad48104 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 e7c9b66ec546..aee0d842be62 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -15,6 +15,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.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;
@@ -204,35 +158,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	 */
 	stmt->tableElts = NIL;
 
+	/*
+	 * Initial relation has no attributes, these can be added later via the
+	 * "init" AM callback.
+	 */
+	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 */
-	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)
@@ -281,10 +220,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;
@@ -295,7 +230,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))
@@ -304,40 +238,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 */
@@ -346,105 +248,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
  *
@@ -456,10 +259,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;
@@ -468,7 +268,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,
@@ -495,16 +295,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,
@@ -514,32 +306,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 */
@@ -568,8 +338,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
@@ -584,10 +352,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);
 }
@@ -650,24 +415,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);
@@ -712,105 +468,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;
@@ -820,69 +480,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;
@@ -972,9 +569,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,
@@ -1008,9 +602,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),
@@ -1032,37 +623,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);
 }
@@ -1203,62 +765,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
@@ -1846,19 +1352,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));
@@ -1885,6 +1388,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);
@@ -1902,17 +1406,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..8d1195de2637 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/seqlocaldesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.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..ced343e3aba7 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocal_xlog.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
@@ -24,7 +25,6 @@
 #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 f26d75e01cfd..2495a28d26a1 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

v25-0003-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From 5c03c66ed7c2ac8581ee4e75b65b6f824969b169 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 7 Nov 2025 15:07:58 +0900
Subject: [PATCH v25 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               |  24 +--
 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, 698 insertions(+), 188 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 50e8f0373dd8..000000000000
--- a/src/include/access/seqlocalam.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * seqlocalam.h
- *	  Local sequence access method.
- *
- * Portions Copyright (c) 1996-2025, 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..1cfc28ca62e1
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,184 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2025, 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 26d15928a155..8f076fcec958 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 6e98a0930c27..080bea5031a9 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 07d182da796a..012a2863c037 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 fd9448ec7b98..6850cf07c7dc 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',
@@ -7914,6 +7920,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 cb730aeac864..d0054c5c988c 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 f3432b4b6a15..317660eccf69 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -145,6 +145,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 3f8d353c49e7..b139f3f479e0 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 ea36cb0fda40..c0311fbe8220 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 d9e868d61796..13cc5c6f4ce5 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3255,6 +3255,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 82ac8646a8d4..ab5e2eab0401 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 80286076a111..ee6c0559deec 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 a0e0437ec063..ea843721bcc5 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 b5f3b2707f56..8719b4d2a201 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/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -26,6 +26,7 @@
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -231,10 +232,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;
@@ -418,7 +419,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 "heap";
@@ -433,7 +434,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];
@@ -500,7 +501,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;
@@ -548,7 +549,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;
@@ -601,7 +602,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,
@@ -628,7 +629,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;
@@ -639,3 +640,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 8b5303553702..f30317d1feae 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..c3b43507f9af
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,146 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2025, 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 265cc3e5fbf4..1f0ed792ca70 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 58ed9d216cc0..e44633d13b60 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 aee0d842be62..f8bb3fd5bfe8 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -15,10 +15,10 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
-#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -150,6 +150,7 @@ 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;
@@ -171,7 +172,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)
@@ -239,7 +240,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 */
@@ -296,7 +297,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,
@@ -309,7 +310,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 */
@@ -352,7 +353,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);
 }
@@ -469,8 +470,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;
@@ -624,7 +625,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);
 }
@@ -1356,8 +1357,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);
@@ -1406,7 +1406,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 973f8073f858..046ba5022a48 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 9ecddb142314..0c79454f641e 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 28f4e11e30ff..7f7e3caca739 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 375b40b29af8..3280ab94f4ee 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 317a1f2b282f..68f160dda7d3 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 2d0cb7bcfd4a..346866c2730c 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);
 
@@ -1831,17 +1835,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.
@@ -1872,6 +1868,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
  *
@@ -3711,14 +3750,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
@@ -4343,13 +4385,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);
@@ -6425,8 +6475,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);
@@ -6438,6 +6490,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 3b9d8349078b..7f0abedb896f 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 f87b558c2c66..35833df35dd8 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 36f245028429..d9b664dcd09e 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 b1ff6f6cd949..f737078bb1fb 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");
@@ -3414,7 +3414,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");
@@ -3712,7 +3712,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 a357e1d0c0e1..c2efb121f2d2 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1943,6 +1943,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 943e56506bf1..f36e267c4b98 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 df795759bb4c..dbaceb9fd2c8 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 04845d5e6809..39a92228e905 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
@@ -3815,6 +3816,7 @@ list_sort_comparator
 loc_chunk
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 local_ts_iter
 local_ts_radix_tree
@@ -4104,7 +4106,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
@@ -4335,6 +4336,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
@@ -4346,7 +4348,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

v25-0004-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From f4ae7b187eb7107f893a365995402fdbf0f97973 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 15 Aug 2025 14:35:50 +0900
Subject: [PATCH v25 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 24ad201af2f9..6098276ae514 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 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 8fa049303990..5c0434ced2c4 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 e33aa95f6ffc..11046352e889 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

v25-0005-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From d85ed5418f8555db2742ad3d893745c8896f60c9 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v25 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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 405c9689bd09..926f16e9beb6 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9842,6 +9842,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..3067dc4d4df0 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>SEQUENCE</literal> and
+      <literal>INDEX</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..a96170bfac03
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.51.0

v25-0006-Refactor-logic-for-page-manipulations-of-sequenc.patchtext/x-diff; charset=us-asciiDownload
From fd230b4d0ef5de2933259245b62c8d0933b635d5 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 11:29:39 +0900
Subject: [PATCH v25 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 8719b4d2a201..3e04c3f90319 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/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -83,27 +84,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
@@ -122,8 +107,6 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 		MarkBufferDirtyHint(*buf, true);
 	}
 
-	seq = (Form_pg_seq_local_data) GETSTRUCT(seqdatatuple);
-
 	return seq;
 }
 
@@ -162,33 +145,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

v25-0007-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From 1ef36121ae5e253930a63ae8357c8d0f5b99bba1 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 30 Oct 2025 16:32:47 +0900
Subject: [PATCH v25 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 ed30ee7d639f..e3bd2b464172 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..bb0270529852
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2025, 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 += bloom
+
+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..91eb7a8995c4
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,519 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2025, 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

#56Xuneng Zhou
xunengzhou@gmail.com
In reply to: Michael Paquier (#55)
Re: Sequence Access Methods, round two

Hi Michael,

On Thu, Dec 18, 2025 at 12:53 PM Michael Paquier <michael@paquier.xyz> wrote:

On Thu, Nov 13, 2025 at 08:51:32AM +0900, Michael Paquier wrote:

Err, no. It's the opposite here: code cleanups and file splits are
cleaner if they happen first, not after the implementations as these
lead to less code churn overall. Splitting the sequence "core" logic
and WAL logic make sense in the long-term to me anyway, as a separate
refactoring piece. It's true that 0002 could be slightly different,
though, we could for example keep sequence.c where it is now in
src/backend/commands/ without forcing the use of the term "AM" in the
file names, and extract the WAL pieces of it into a new file (aka the
redo and marking routines). Then it's only a game of moving the files
around depending on the follow-up pieces. I should probably post a
patch for that separately, this has been bugging me a bit in terms of
code separation clarity for the sequence RMGR.

Since this update, the code related to the sequence RMGR has been
moved around with a87987cafca6. This makes the rebased version of the
patch leaner, with a cleaner split between the WAL and "core"
computation logic, as v24 and older patch sets submitted on this
thread were splitting this code already.

Anyway. Rebased. v25. Attached.

Thanks for working on this. I tried to review patch set v25, but I
wasn’t able to apply it cleanly on HEAD.

On an initial look, here are a few minor comments on patches 1 and 2.

1. Duplicate macro definition in seqlocalam.c:

+#define SEQ_LOCAL_LOG_VALS 32
...
+#define SEQ_LOCAL_LOG_VALS 32

We have two macros the same here.

2. Duplicate stmt->tableElts = NIL; in sequence.c:
*/
stmt->tableElts = NIL;

+ /*
+ * Initial relation has no attributes, these can be added later via the
+ * "init" AM callback.
+ */
+ stmt->tableElts = NIL;

The same assignment appears twice - the second seems to be redundant.

3. Potential error message inconsistency in seqlocalxlog.c:

if (info != XLOG_SEQ_LOCAL_LOG)
elog(PANIC, "seq_redo: unknown op code %u", info); // Says
"seq_redo" not "seq_local_redo"

Should we update this to "seq_local_redo: unknown op code %u”?

--
Best,
Xuneng

#57Michael Paquier
michael@paquier.xyz
In reply to: Xuneng Zhou (#56)
7 attachment(s)
Re: Sequence Access Methods, round two

On Fri, Dec 19, 2025 at 02:45:47PM +0800, Xuneng Zhou wrote:

Thanks for working on this. I tried to review patch set v25, but I
wasn’t able to apply it cleanly on HEAD.

Strange. It rebases correctly even on today's HEAD at 5cdbec5aa9dc.

We have two macros the same here.

2. Duplicate stmt->tableElts = NIL; in sequence.c:

It looks like I have fat-fingered some rebases here.

Should we update this to "seq_local_redo: unknown op code %u”?

Yep, thanks.
--
Michael

Attachments:

v26-0001-Integrate-addition-of-attributes-for-sequences-w.patchtext/x-diff; charset=us-asciiDownload
From 24d415449b97f5cf31ed974e2d81f5b5b9742bdd Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Jan 2024 15:00:14 +0900
Subject: [PATCH v26 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               | 29 +++++++++++++++++--
 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, 69 insertions(+), 8 deletions(-)

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index bc7adba4a0fc..d9e868d61796 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2441,6 +2441,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 51567994126f..e7c9b66ec546 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;
@@ -197,12 +199,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	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 6b1a00ed477d..973f8073f858 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4653,6 +4653,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 */
@@ -4953,6 +4954,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,
@@ -5391,6 +5399,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,
@@ -6622,6 +6631,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 d18a3a60a467..9eaf16a80466 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1666,7 +1666,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 17d72e412ff8..453d02aead62 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

v26-0002-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From 48ee4b1af423f89411387be712a71d4bf3a78fa6 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 19 Dec 2025 16:14:59 +0900
Subject: [PATCH v26 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}    |  22 +-
 src/backend/access/sequence/Makefile          |   4 +-
 src/backend/access/sequence/meson.build       |   2 +
 .../sequence/seqlocal_xlog.c}                 |  55 +-
 src/backend/access/sequence/seqlocalam.c      | 634 ++++++++++++++++++
 src/backend/access/transam/rmgr.c             |   2 +-
 src/backend/commands/Makefile                 |   1 -
 src/backend/commands/meson.build              |   1 -
 src/backend/commands/sequence.c               | 565 +---------------
 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, 790 insertions(+), 630 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 8e7fc9db8778..b942b25350bc 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..02c172135d29
--- /dev/null
+++ b/src/include/access/seqlocal_xlog.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocal_xlog.h
+ *	  Local sequence WAL definitions.
+ *
+ * Portions Copyright (c) 1996-2025, 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..50e8f0373dd8
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2025, 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 c8cf0112c382..000000000000
--- a/src/include/commands/sequence_xlog.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * sequence_xlog.h
- *	  Sequence WAL definitions.
- *
- * Portions Copyright (c) 1996-2025, 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 96c98e800c22..6b3b3e1e44c1 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 a0edb78856bd..6c0dc8c009d2 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-2025, 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 ec9ab9b7e9db..a0e0437ec063 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2025, 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 ffbd9820416a..290f7f02dec2 100644
--- a/src/backend/commands/sequence_xlog.c
+++ b/src/backend/access/sequence/seqlocal_xlog.c
@@ -1,26 +1,38 @@
 /*-------------------------------------------------------------------------
  *
- * sequence.c
- *	  RMGR WAL routines for sequences.
+ * seqlocalxlog.c
+ *	  WAL replay logic for 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/commands/sequence_xlog.c
+ *        src/backend/access/sequence/seqlocalxlog.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"
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+seq_local_mask(char *page, BlockNumber blkno)
+{
+	mask_page_lsn_and_checksum(page);
+
+	mask_unused_space(page);
+}
 
 void
-seq_redo(XLogReaderState *record)
+seq_local_redo(XLogReaderState *record)
 {
 	XLogRecPtr	lsn = record->EndRecPtr;
 	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
@@ -29,11 +41,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 +61,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);
 
@@ -67,14 +79,3 @@ seq_redo(XLogReaderState *record)
 
 	pfree(localpage);
 }
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
-	mask_page_lsn_and_checksum(page);
-
-	mask_unused_space(page);
-}
diff --git a/src/backend/access/sequence/seqlocalam.c b/src/backend/access/sequence/seqlocalam.c
new file mode 100644
index 000000000000..f26a37744f6f
--- /dev/null
+++ b/src/backend/access/sequence/seqlocalam.c
@@ -0,0 +1,634 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/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 "heap";
+}
+
+/*
+ * 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..9ffb2f07530d 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,13 +27,13 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocal_xlog.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.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 5fc35826b1cc..9f640ad48104 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 e7c9b66ec546..129159388ceb 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -15,6 +15,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.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;
@@ -199,6 +153,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->oncommit = ONCOMMIT_NOOP;
 	stmt->tablespacename = NULL;
 	stmt->if_not_exists = seq->if_not_exists;
+
 	/*
 	 * Initial relation has no attributes, these are added later.
 	 */
@@ -210,29 +165,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)
@@ -281,10 +215,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;
@@ -295,7 +225,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))
@@ -304,40 +233,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 */
@@ -346,105 +243,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
  *
@@ -456,10 +254,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;
@@ -468,7 +263,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,
@@ -495,16 +290,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,
@@ -514,32 +301,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 */
@@ -568,8 +333,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
@@ -584,10 +347,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);
 }
@@ -650,24 +410,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);
@@ -712,105 +463,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;
@@ -820,69 +475,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;
@@ -972,9 +564,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,
@@ -1008,9 +597,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),
@@ -1032,37 +618,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);
 }
@@ -1203,62 +760,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
@@ -1846,19 +1347,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));
@@ -1885,6 +1383,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);
@@ -1902,17 +1401,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..8d1195de2637 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
 /gistdesc.c
 /hashdesc.c
 /heapdesc.c
+/seqlocaldesc.c
 /logicalmsgdesc.c
 /mxactdesc.c
 /nbtdesc.c
 /relmapdesc.c
 /replorigindesc.c
 /rmgrdesc_utils.c
-/seqdesc.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..ced343e3aba7 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocal_xlog.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.h"
 #include "access/rmgr.h"
@@ -24,7 +25,6 @@
 #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 f26d75e01cfd..2495a28d26a1 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

v26-0003-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From 77b84135704a9d07fe3c058a71fbd55e6207dfcf Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 7 Nov 2025 15:07:58 +0900
Subject: [PATCH v26 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               |  24 +--
 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, 698 insertions(+), 188 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 50e8f0373dd8..000000000000
--- a/src/include/access/seqlocalam.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * seqlocalam.h
- *	  Local sequence access method.
- *
- * Portions Copyright (c) 1996-2025, 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..1cfc28ca62e1
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,184 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2025, 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 26d15928a155..8f076fcec958 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 6e98a0930c27..080bea5031a9 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 07d182da796a..012a2863c037 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 fd9448ec7b98..6850cf07c7dc 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',
@@ -7914,6 +7920,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 cb730aeac864..d0054c5c988c 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 f3432b4b6a15..317660eccf69 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -145,6 +145,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 3f8d353c49e7..b139f3f479e0 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 ea36cb0fda40..c0311fbe8220 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 d9e868d61796..13cc5c6f4ce5 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3255,6 +3255,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 82ac8646a8d4..ab5e2eab0401 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 80286076a111..ee6c0559deec 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 a0e0437ec063..ea843721bcc5 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 f26a37744f6f..10f89142ab94 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/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -26,6 +26,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 */
@@ -224,10 +225,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;
@@ -411,7 +412,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 "heap";
@@ -426,7 +427,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];
@@ -493,7 +494,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;
@@ -541,7 +542,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;
@@ -594,7 +595,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,
@@ -621,7 +622,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;
@@ -632,3 +633,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 8b5303553702..f30317d1feae 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..c3b43507f9af
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,146 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2025, 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 265cc3e5fbf4..1f0ed792ca70 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 58ed9d216cc0..e44633d13b60 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "access/sequenceam.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.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 129159388ceb..ab6ad9e0d517 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -15,10 +15,10 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
-#include "access/seqlocalam.h"
 #include "access/multixact.h"
 #include "access/relation.h"
 #include "access/sequence.h"
+#include "access/sequenceam.h"
 #include "access/table.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -150,6 +150,7 @@ 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;
@@ -166,7 +167,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)
@@ -234,7 +235,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 */
@@ -291,7 +292,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,
@@ -304,7 +305,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 */
@@ -347,7 +348,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);
 }
@@ -464,8 +465,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;
@@ -619,7 +620,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);
 }
@@ -1351,8 +1352,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);
@@ -1401,7 +1401,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 973f8073f858..046ba5022a48 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 9ecddb142314..0c79454f641e 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 28f4e11e30ff..7f7e3caca739 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 2b7b084f2162..acaf83823e7f 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 317a1f2b282f..68f160dda7d3 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 2d0cb7bcfd4a..346866c2730c 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);
 
@@ -1831,17 +1835,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.
@@ -1872,6 +1868,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
  *
@@ -3711,14 +3750,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
@@ -4343,13 +4385,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);
@@ -6425,8 +6475,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);
@@ -6438,6 +6490,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 3b9d8349078b..7f0abedb896f 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 f87b558c2c66..35833df35dd8 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 36f245028429..d9b664dcd09e 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 b1ff6f6cd949..f737078bb1fb 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");
@@ -3414,7 +3414,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");
@@ -3712,7 +3712,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 a357e1d0c0e1..c2efb121f2d2 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1943,6 +1943,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 943e56506bf1..f36e267c4b98 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 df795759bb4c..dbaceb9fd2c8 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 04845d5e6809..39a92228e905 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
@@ -3815,6 +3816,7 @@ list_sort_comparator
 loc_chunk
 local_relopt
 local_relopts
+local_sequence_magic
 local_source
 local_ts_iter
 local_ts_radix_tree
@@ -4104,7 +4106,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
@@ -4335,6 +4336,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
@@ -4346,7 +4348,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

v26-0004-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From 0811dfeeb06df53d615049232f994ee24828fca7 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 15 Aug 2025 14:35:50 +0900
Subject: [PATCH v26 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 27f6be3f0f82..ecbdda06561e 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 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 8fa049303990..5c0434ced2c4 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 e33aa95f6ffc..11046352e889 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

v26-0005-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From a3b2206cb6fc64ce1ddb0514172091891f516562 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:56 +0900
Subject: [PATCH v26 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               | 80 ++++++++++++++++++++++
 6 files changed, 119 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 405c9689bd09..926f16e9beb6 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9842,6 +9842,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..3067dc4d4df0 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>SEQUENCE</literal> and
+      <literal>INDEX</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..a96170bfac03
--- /dev/null
+++ b/doc/src/sgml/sequenceam.sgml
@@ -0,0 +1,80 @@
+<!-- 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 entirely new access method types 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>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
+ </para>
+</chapter>
-- 
2.51.0

v26-0006-Refactor-logic-for-page-manipulations-of-sequenc.patchtext/x-diff; charset=us-asciiDownload
From 986485768c1888eadfdc2db17bb9bc29b981a211 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 11:29:39 +0900
Subject: [PATCH v26 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 10f89142ab94..aad3a2c8c14c 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/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -76,27 +77,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
@@ -115,8 +100,6 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
 		MarkBufferDirtyHint(*buf, true);
 	}
 
-	seq = (Form_pg_seq_local_data) GETSTRUCT(seqdatatuple);
-
 	return seq;
 }
 
@@ -155,33 +138,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

v26-0007-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From 36722ca79a4243f77b92ebcfe301a8dab4e6c489 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 30 Oct 2025 16:32:47 +0900
Subject: [PATCH v26 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 ed30ee7d639f..e3bd2b464172 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..bb0270529852
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2025, 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 += bloom
+
+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..91eb7a8995c4
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,519 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2025, 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

#58Chao Li
li.evan.chao@gmail.com
In reply to: Michael Paquier (#57)
Re: Sequence Access Methods, round two

On Dec 19, 2025, at 15:33, Michael Paquier <michael@paquier.xyz> wrote:

On Fri, Dec 19, 2025 at 02:45:47PM +0800, Xuneng Zhou wrote:

Thanks for working on this. I tried to review patch set v25, but I
wasn’t able to apply it cleanly on HEAD.

Strange. It rebases correctly even on today's HEAD at 5cdbec5aa9dc.

We have two macros the same here.

2. Duplicate stmt->tableElts = NIL; in sequence.c:

It looks like I have fat-fingered some rebases here.

Should we update this to "seq_local_redo: unknown op code %u”?

Yep, thanks.
--
Michael
<v26-0001-Integrate-addition-of-attributes-for-sequences-w.patch><v26-0002-Refactor-code-for-in-core-local-sequences.patch><v26-0003-Sequence-access-methods-backend-support.patch><v26-0004-Sequence-access-methods-dump-restore-support.patch><v26-0005-Sequence-access-methods-core-documentation.patch><v26-0006-Refactor-logic-for-page-manipulations-of-sequenc.patch><v26-0007-snowflake-Add-sequence-AM-based-on-it.patch>

Hi Michael,

I just started to review this patch. While reviewing 0001, I got the point where you now add the 3 attributes into pg_attributes, so I tried to test this behavior, but a basic “create sequence” command failed for me:
```
evantest=# create sequence ts1 start with 1 increment by 1 cache 10 cycle;
ERROR: access method "seqlocal" does not exist
```

Did I do something wrong?

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

#59zengman
zengman@halodbtech.com
In reply to: Michael Paquier (#57)
Re: Sequence Access Methods, round two

Hi Michael,

I noticed what appears to be an obvious minor error in patch 0007: in `contrib/snowflake/meson.build`,
the line should not be `contrib_targets += bloom` – it ought to be revised to `contrib_targets += snowflake`.

--
Regards,
Man Zeng
www.openhalo.org

#60zengman
zengman@halodbtech.com
In reply to: zengman (#59)
Re: Sequence Access Methods, round two

Additionally, it may be necessary to replace the line `PG_MODULE_MAGIC`; in `contrib/snowflake/snowflake.c` with the following code block:
```
PG_MODULE_MAGIC_EXT(
.name = "snowflake",
.version = PG_VERSION
);
```
In addition, the memory allocation code within the `snowflake_get` function:
```
nulls = palloc0(sizeof(bool) * tupdesc->natts);
values = palloc0(sizeof(Datum) * tupdesc->natts);
```
should be replaced with:
```
nulls = palloc0_array(sizeof(bool), tupdesc->natts);
values = palloc0_array(sizeof(Datum), tupdesc->natts);
```

--
Regards,
Man Zeng
www.openhalo.org

#61Michael Paquier
michael@paquier.xyz
In reply to: Chao Li (#58)
Re: Sequence Access Methods, round two

On Tue, Dec 23, 2025 at 02:55:45PM +0800, Chao Li wrote:

I just started to review this patch. While reviewing 0001, I got the
point where you now add the 3 attributes into pg_attributes, so I
tried to test this behavior, but a basic “create sequence” command
failed for me:
```
evantest=# create sequence ts1 start with 1 increment by 1 cache 10 cycle;
ERROR: access method "seqlocal" does not exist
```

Did I do something wrong?

I guess so. 0001 taken independently has no link to seqlocal.
Applying the full set of patches works here:
=# create sequence ts1 start with 1 increment by 1 cache 10 cycle;
CREATE SEQUENCE

I doubt that the regression would pass at all with this class of
failures. You may need to clean your tree and rebuild.
--
Michael

#62Chao Li
li.evan.chao@gmail.com
In reply to: Michael Paquier (#61)
Re: Sequence Access Methods, round two

On Dec 23, 2025, at 16:42, Michael Paquier <michael@paquier.xyz> wrote:

On Tue, Dec 23, 2025 at 02:55:45PM +0800, Chao Li wrote:

I just started to review this patch. While reviewing 0001, I got the
point where you now add the 3 attributes into pg_attributes, so I
tried to test this behavior, but a basic “create sequence” command
failed for me:
```
evantest=# create sequence ts1 start with 1 increment by 1 cache 10 cycle;
ERROR: access method "seqlocal" does not exist
```

Did I do something wrong?

I guess so. 0001 taken independently has no link to seqlocal.
Applying the full set of patches works here:
=# create sequence ts1 start with 1 increment by 1 cache 10 cycle;
CREATE SEQUENCE

I doubt that the regression would pass at all with this class of
failures. You may need to clean your tree and rebuild.
--
Michael

I have applied the full patch set. So, I guess I need a clean rebuild. I will try again.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

#63Chao Li
li.evan.chao@gmail.com
In reply to: Chao Li (#62)
Re: Sequence Access Methods, round two

On Dec 23, 2025, at 16:48, Chao Li <li.evan.chao@gmail.com> wrote:

On Dec 23, 2025, at 16:42, Michael Paquier <michael@paquier.xyz> wrote:

On Tue, Dec 23, 2025 at 02:55:45PM +0800, Chao Li wrote:

I just started to review this patch. While reviewing 0001, I got the
point where you now add the 3 attributes into pg_attributes, so I
tried to test this behavior, but a basic “create sequence” command
failed for me:
```
evantest=# create sequence ts1 start with 1 increment by 1 cache 10 cycle;
ERROR: access method "seqlocal" does not exist
```

Did I do something wrong?

I guess so. 0001 taken independently has no link to seqlocal.
Applying the full set of patches works here:
=# create sequence ts1 start with 1 increment by 1 cache 10 cycle;
CREATE SEQUENCE

I doubt that the regression would pass at all with this class of
failures. You may need to clean your tree and rebuild.
--
Michael

I have applied the full patch set. So, I guess I need a clean rebuild. I will try again.

Okay, a clean build has made the patch working for me. I did some basic tests and went through all commits. After 26 rounds of revision, this patch now looks solid already. Here comes my comments:

1 - 0001
```
+	/*
+	 * Initial relation has no attributes, these are added later.
+	 */
+	stmt->tableElts = NIL;
```

When I read the first few commits, I was thinking why cannot we still add the 3 attributes while creating the sequence relation. Finally, I got the idea: creating an empty sequence relation and adding attributes later enables different access method to add different attributes.

I think "these are added later” is too simple, it deserves more words, which would make readers easier to understand the change.

2 - 0002
```
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -27,13 +27,13 @@
 #include "access/gistxlog.h"
 #include "access/hash_xlog.h"
 #include "access/heapam_xlog.h"
+#include "access/seqlocal_xlog.h"
 #include "access/multixact.h"
 #include "access/nbtxlog.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”
```

AFAIK, headers files should placed in alphabetical order, so "access/seqlocal_xlog.h” should be placed after "access/nbtxlog.h”. You can see all existing includes follow alphabetical order.

3 - 0002 - seqlocalam.c
```
+/*
+ * 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)
+{
```

This function handles unlogged logic, but should UNLOGGED be handled by the core?

4 - 0002
```
+const char *
+seq_local_get_table_am(void)
+{
+	return "heap";
+}
```

I am thinking if we should use the constant DEFAULT_TABLE_ACCESS_METHOD that is also “heap” today. My theory is that, in future, PG introduces a new table access method, say “heap_ex” (enhanced heap”), and switch to use it, DEFAULT_TABLE_ACCESS_METHOD will be updated to “heap_ex”, then I guess local sequence am should switch to use “heap_ex” for table am as well.

5 - 0003
```
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@

#include "access/htup_details.h"
#include "access/table.h"
+#include "access/sequenceam.h"
#include "catalog/catalog.h”
```

"access/sequenceam.h” should be placed before "access/table.h”.

6 - 0004
```
+ printf(_(" --no-sequence-access-method do not sequence table access methods\n"));
```

Seems a copy/paste bug, missing “dump” after “do not”.

7 - 0005 - sequenceam.sgml
```
+ methods</firstterm>, which manage the operations around sequences . The core
```

An unnecessary white-space between “sequences” and “.”.

8 - 0005 - sequenceam.sgml
```
+ so it is possible to develop entirely new access method types by writing
```

* “entirely” seems not necessary
* “types” seems not needed, maybe just “new access methods”. Because types might lead to confusion with table/index AM types.

9 - 0005 - config.sgml
```
+        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>.
```

Should default be “seqlocal”?

10 - 0005 - sequenceam.sgml
```
+  Any developer of a new <literal>sequence access method</literal> can refer to
+  the existing <literal>local</literal> implementation present in
+  <filename>src/backend/access/sequence/local.c</filename> for details of
+  its implementation.
```

“local.c” => “seqlocalam.c”

11 - 0005 - create_access_method.sgml
```
-      Only <literal>TABLE</literal> and <literal>INDEX</literal>
-      are supported at present.
+      Only <literal>TABLE</literal>, <literal>SEQUENCE</literal> and
+      <literal>INDEX</literal> are supported at present.
```

Maybe put INDEX before SEQUENCE, because INDEX is more important than SEQUENCE.

12 - 0006
```
+#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)
```

This macro assumes the caller has buf, page, sm, tuple, rel and forkNum, which makes the macro fragile. Actually, buf, page and sm are purely local and temp, they can be defined within the do{}while block, and tuple, rel and forkNum can be passed in as arguments. Also, given this macro is relatively long, maybe consider an inline static function.

13 - 0006
```
+#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)
```

This macro not only assumes the caller has buf, also update buf. Maybe a static inline function is better here.

14 - 0007 - contrib/snowflake/meson.build
```
+contrib_targets += bloom
```

Looks like a copy/paste bug, should “bloom” be “snowflake”?

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

#64Michael Paquier
michael@paquier.xyz
In reply to: Chao Li (#63)
7 attachment(s)
Re: Sequence Access Methods, round two

On Wed, Dec 24, 2025 at 04:14:06PM +0800, Chao Li wrote:

Okay, a clean build has made the patch working for me. I did some
basic tests and went through all commits. After 26 rounds of
revision, this patch now looks solid already. Here comes my
comments:

Thanks for the review.

3 - 0002 - seqlocalam.c
+static void
+fill_seq_with_data(Relation rel, HeapTuple tuple)
+{
```

This function handles unlogged logic, but should UNLOGGED be handled
by the core?

That is something I have considered while splitting the code, but this
makes the init() and reset() callbacks weirder in shape as, so the
separation felt more natural to me, leaving up to the AM layer what
should be done. So the answer from me is nope on this one.

I am thinking if we should use the constant
DEFAULT_TABLE_ACCESS_METHOD that is also “heap” today. My theory is
that, in future, PG introduces a new table access method, say
“heap_ex” (enhanced heap”), and switch to use it,
DEFAULT_TABLE_ACCESS_METHOD will be updated to “heap_ex”, then I
guess local sequence am should switch to use “heap_ex” for table am
as well.

Using DEFAULT_TABLE_ACCESS_METHOD makes sense I guess. I doubt that
we'll ever rename that, though.

+ printf(_(" --no-sequence-access-method do not sequence table access methods\n"));
Seems a copy/paste bug, missing “dump” after “do not”.

Oops, fixed.

7 - 0005 - sequenceam.sgml
8 - 0005 - sequenceam.sgml
9 - 0005 - config.sgml
10 - 0005 - sequenceam.sgml
11 - 0005 - create_access_method.sgml

Yeah, fixed these ones. For the last one, I am not really sure how
much the ordering matters, but I guess that your suggestion is fine as
well.

12 - 0006
This macro assumes the caller has buf, page, sm, tuple, rel and
forkNum, which makes the macro fragile. Actually, buf, page and sm
are purely local and temp, they can be defined within the do{}while
block, and tuple, rel and forkNum can be passed in as
arguments. Also, given this macro is relatively long, maybe consider
an inline static function.

13 - 0006
This macro not only assumes the caller has buf, also update
buf. Maybe a static inline function is better here.

These two are intentional for one reason: it makes people aware that
pages should have a special area enforced with a specific type and
that the special area should be cross-checked on read. It is true
that in the init() case we could return a Buffer and pass the size of
the special area, setting the contents of the special area outside,
but the second case brings trouble as the special area should be
checked before reading any fields from the page. Perhaps the case for
this layer is not that much justified, this only mimics the core
sequence method that relies on a single page per sequence with a
buffer lock when getting a value.

14 - 0007 - contrib/snowflake/meson.build
```
+contrib_targets += bloom
```

Looks like a copy/paste bug, should “bloom” be “snowflake”?

Indeed.
--
Michael

Attachments:

v27-0001-Integrate-addition-of-attributes-for-sequences-w.patchtext/x-diff; charset=us-asciiDownload
From 5336707cd21397c0d123a4fabd0cd76da4843b46 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 26 Dec 2025 11:15:22 +0900
Subject: [PATCH v27 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 bc7adba4a0fc..d9e868d61796 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2441,6 +2441,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 51567994126f..ce75fe72b214 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 1d9565b09fcd..b321861640b9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4653,6 +4653,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 */
@@ -4953,6 +4954,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,
@@ -5391,6 +5399,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,
@@ -6622,6 +6631,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 d18a3a60a467..9eaf16a80466 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1666,7 +1666,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 17d72e412ff8..453d02aead62 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

v27-0002-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From cf98a350a7c83e4b279e7b9ad55eea28081d862f Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 26 Dec 2025 11:39:44 +0900
Subject: [PATCH v27 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 8e7fc9db8778..b942b25350bc 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..02c172135d29
--- /dev/null
+++ b/src/include/access/seqlocal_xlog.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocal_xlog.h
+ *	  Local sequence WAL definitions.
+ *
+ * Portions Copyright (c) 1996-2025, 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..50e8f0373dd8
--- /dev/null
+++ b/src/include/access/seqlocalam.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqlocalam.h
+ *	  Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2025, 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 c8cf0112c382..000000000000
--- a/src/include/commands/sequence_xlog.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * sequence_xlog.h
- *	  Sequence WAL definitions.
- *
- * Portions Copyright (c) 1996-2025, 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 96c98e800c22..6b3b3e1e44c1 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 a0edb78856bd..9e0b4f8f21d7 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-2025, 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 ec9ab9b7e9db..a0e0437ec063 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
 # Copyright (c) 2022-2025, 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 ffbd9820416a..7bcb877e17ff 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-2025, 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 5fc35826b1cc..9f640ad48104 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 ce75fe72b214..4a8be58b7164 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 f26d75e01cfd..2495a28d26a1 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

v27-0003-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From 28dda6813f46861e0850da8e065841d809682302 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 26 Dec 2025 11:55:37 +0900
Subject: [PATCH v27 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 50e8f0373dd8..000000000000
--- a/src/include/access/seqlocalam.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * seqlocalam.h
- *	  Local sequence access method.
- *
- * Portions Copyright (c) 1996-2025, 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..1cfc28ca62e1
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,184 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ *	  POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2025, 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 26d15928a155..8f076fcec958 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 6e98a0930c27..080bea5031a9 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 07d182da796a..012a2863c037 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 fd9448ec7b98..6850cf07c7dc 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',
@@ -7914,6 +7920,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 cb730aeac864..d0054c5c988c 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 f3432b4b6a15..317660eccf69 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -145,6 +145,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 3f8d353c49e7..b139f3f479e0 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 ea36cb0fda40..c0311fbe8220 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 d9e868d61796..13cc5c6f4ce5 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3255,6 +3255,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 fbe0b1e2e3dc..b2b96d0d8d70 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 80286076a111..ee6c0559deec 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 a0e0437ec063..ea843721bcc5 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 8b5303553702..f30317d1feae 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..c3b43507f9af
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,146 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ *	  general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2025, 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 265cc3e5fbf4..1f0ed792ca70 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 58ed9d216cc0..00bf78da0a0b 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 4a8be58b7164..3e443ec15f4f 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 b321861640b9..72243ffd7f73 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 9ecddb142314..0c79454f641e 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 28f4e11e30ff..7f7e3caca739 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 2b7b084f2162..acaf83823e7f 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 317a1f2b282f..68f160dda7d3 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 2d0cb7bcfd4a..346866c2730c 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);
 
@@ -1831,17 +1835,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.
@@ -1872,6 +1868,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
  *
@@ -3711,14 +3750,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
@@ -4343,13 +4385,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);
@@ -6425,8 +6475,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);
@@ -6438,6 +6490,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 ac0c7c36c561..cfaf22186d66 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 04ab0a266088..f121b61a0a84 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 36f245028429..d9b664dcd09e 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 75a101c6ab51..82d5a675b862 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 a357e1d0c0e1..c2efb121f2d2 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1943,6 +1943,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 943e56506bf1..f36e267c4b98 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 df795759bb4c..dbaceb9fd2c8 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 5c88fa92f4e5..78ef2173236c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2729,6 +2729,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

v27-0004-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From 01530615d0f16dafd3c3978296631c2f973597eb Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 26 Dec 2025 11:56:30 +0900
Subject: [PATCH v27 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 27f6be3f0f82..72af74adf556 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 8fa049303990..5c0434ced2c4 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 e33aa95f6ffc..11046352e889 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

v27-0005-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From 2fdf22c092eeec9d411d00442542b604fe0a7b40 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 26 Dec 2025 12:05:36 +0900
Subject: [PATCH v27 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 cdfe8e376f0c..75c00bbed845 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9854,6 +9854,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

v27-0006-Refactor-logic-for-page-manipulations-of-sequenc.patchtext/x-diff; charset=us-asciiDownload
From 7ef9ade782bb1a5c4a5a92b65578910855b49ed6 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 21 Aug 2025 11:29:39 +0900
Subject: [PATCH v27 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

v27-0007-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From 4b890efd2639246a9891fbf65e01d9b22877dbca Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 26 Dec 2025 14:32:08 +0900
Subject: [PATCH v27 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 ed30ee7d639f..e3bd2b464172 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..2d8d5b94ae07
--- /dev/null
+++ b/contrib/snowflake/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2025, 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..91eb7a8995c4
--- /dev/null
+++ b/contrib/snowflake/snowflake.c
@@ -0,0 +1,519 @@
+/*-------------------------------------------------------------------------
+ *
+ * snowflake.c
+ *
+ * Portions Copyright (c) 1996-2025, 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

#65Chao Li
li.evan.chao@gmail.com
In reply to: Michael Paquier (#64)
Re: Sequence Access Methods, round two

On Dec 26, 2025, at 13:45, Michael Paquier <michael@paquier.xyz> wrote:

On Wed, Dec 24, 2025 at 04:14:06PM +0800, Chao Li wrote:

Okay, a clean build has made the patch working for me. I did some
basic tests and went through all commits. After 26 rounds of
revision, this patch now looks solid already. Here comes my
comments:

Thanks for the review.

3 - 0002 - seqlocalam.c
+static void
+fill_seq_with_data(Relation rel, HeapTuple tuple)
+{
```

This function handles unlogged logic, but should UNLOGGED be handled
by the core?

That is something I have considered while splitting the code, but this
makes the init() and reset() callbacks weirder in shape as, so the
separation felt more natural to me, leaving up to the AM layer what
should be done. So the answer from me is nope on this one.

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.

I am thinking if we should use the constant
DEFAULT_TABLE_ACCESS_METHOD that is also “heap” today. My theory is
that, in future, PG introduces a new table access method, say
“heap_ex” (enhanced heap”), and switch to use it,
DEFAULT_TABLE_ACCESS_METHOD will be updated to “heap_ex”, then I
guess local sequence am should switch to use “heap_ex” for table am
as well.

Using DEFAULT_TABLE_ACCESS_METHOD makes sense I guess. I doubt that
we'll ever rename that, though.

I didn’t mean to suggest that we would ever rename “heap” to “heap_ex”. That was just an example to illustrate that, at some point in the future, a new default table AM might be introduced. If we use the constant today, it reduces the risk of forgetting to update this code when such a change happens.

12 - 0006
This macro assumes the caller has buf, page, sm, tuple, rel and
forkNum, which makes the macro fragile. Actually, buf, page and sm
are purely local and temp, they can be defined within the do{}while
block, and tuple, rel and forkNum can be passed in as
arguments. Also, given this macro is relatively long, maybe consider
an inline static function.

13 - 0006
This macro not only assumes the caller has buf, also update
buf. Maybe a static inline function is better here.

These two are intentional for one reason: it makes people aware that
pages should have a special area enforced with a specific type and
that the special area should be cross-checked on read. It is true
that in the init() case we could return a Buffer and pass the size of
the special area, setting the contents of the special area outside,
but the second case brings trouble as the special area should be
checked before reading any fields from the page. Perhaps the case for
this layer is not that much justified, this only mimics the core
sequence method that relies on a single page per sequence with a
buffer lock when getting a value.

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.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

#66Michael Paquier
michael@paquier.xyz
In reply to: Chao Li (#65)
7 attachment(s)
Re: Sequence Access Methods, round two

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:

v28-0001-Integrate-addition-of-attributes-for-sequences-w.patchtext/x-diff; charset=us-asciiDownload
From ae717c46467bf727c9ac203079750c65eaf4cf9e Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
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

v28-0002-Refactor-code-for-in-core-local-sequences.patchtext/x-diff; charset=us-asciiDownload
From 5a56683838b4950902106e035e0b18605422320a Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
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

v28-0003-Sequence-access-methods-backend-support.patchtext/x-diff; charset=us-asciiDownload
From 44269598ef7d9fa334d953bba558c7c0cb7c7398 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
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

v28-0004-Sequence-access-methods-dump-restore-support.patchtext/x-diff; charset=us-asciiDownload
From 4c6d88d61b0b7715709db0ab6e04e46476a15a08 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
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

v28-0005-Sequence-access-methods-core-documentation.patchtext/x-diff; charset=us-asciiDownload
From 7df3c23b7bab7ae09187e86f83d6a206982f7ca4 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
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

v28-0006-Refactor-logic-for-page-manipulations-of-sequenc.patchtext/x-diff; charset=us-asciiDownload
From 26e42deeea1a773ce543429d000953590e689256 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
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

v28-0007-snowflake-Add-sequence-AM-based-on-it.patchtext/x-diff; charset=us-asciiDownload
From cf3a86b1d0920fe3eb50e17e809db3bf7c134ab6 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
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