public inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH v24 01/10] Built-in compression method
8+ messages / 4 participants
[nested] [flat]

* [PATCH v24 01/10] Built-in compression method
@ 2021-02-05 12:51 dilipkumar <[email protected]>
  0 siblings, 0 replies; 8+ messages in thread

From: dilipkumar @ 2021-02-05 12:51 UTC (permalink / raw)

Add syntax allowing a compression method to be specified.
As of now there is only 2 option for build-in compression
method (pglz, lz4) which can be set while creating a table
or adding a new column.  No option for altering the
compression method for an existing column.

Dilip Kumar based on the patches from Ildus Kurbangaliev.
Design input from Robert Haas and Tomas Vondra.
Reviewed by Robert Haas, Tomas Vondra, Alexander Korotkov and Justin Pryzby

Discussions:
https://www.postgresql.org/message-id/[email protected]
https://www.postgresql.org/message-id/CA%2BTgmoaKDW1Oi9V%3Djc9hOGyf77NbkNEABuqgHD1Cq%3D%3D1QsOcxg%40...
https://www.postgresql.org/message-id/CA%2BTgmobSDVgUage9qQ5P_%3DF_9jaMkCgyKxUQGtFQU7oN4kX-AA%40mail...
https://www.postgresql.org/message-id/20201005160355.byp74sh3ejsv7wrj%40development
https://www.postgresql.org/message-id/CAFiTN-tzTTT2oqWdRGLv1dvvS5MC1W%2BLE%2B3bqWPJUZj4GnHOJg%40mail...
---
 configure                                     | 116 ++++++++++
 configure.ac                                  |  17 ++
 doc/src/sgml/ddl.sgml                         |   3 +
 doc/src/sgml/ref/create_table.sgml            |  25 +++
 src/backend/access/Makefile                   |   2 +-
 src/backend/access/brin/brin_tuple.c          |   5 +-
 src/backend/access/common/detoast.c           | 103 ++++++---
 src/backend/access/common/indextuple.c        |   4 +-
 src/backend/access/common/toast_internals.c   |  63 +++---
 src/backend/access/common/tupdesc.c           |   8 +
 src/backend/access/compression/Makefile       |  17 ++
 src/backend/access/compression/compress_lz4.c | 164 ++++++++++++++
 .../access/compression/compress_pglz.c        | 138 ++++++++++++
 src/backend/access/table/toast_helper.c       |   5 +-
 src/backend/bootstrap/bootstrap.c             |   5 +
 src/backend/catalog/genbki.pl                 |   7 +-
 src/backend/catalog/heap.c                    |   4 +
 src/backend/catalog/index.c                   |   2 +
 src/backend/catalog/toasting.c                |   5 +
 src/backend/commands/amcmds.c                 |  10 +
 src/backend/commands/createas.c               |  14 ++
 src/backend/commands/matview.c                |  14 ++
 src/backend/commands/tablecmds.c              | 111 +++++++++-
 src/backend/executor/nodeModifyTable.c        | 122 +++++++++++
 src/backend/nodes/copyfuncs.c                 |   1 +
 src/backend/nodes/equalfuncs.c                |   1 +
 src/backend/nodes/nodeFuncs.c                 |   2 +
 src/backend/nodes/outfuncs.c                  |   1 +
 src/backend/parser/gram.y                     |  26 ++-
 src/backend/parser/parse_utilcmd.c            |   8 +
 src/backend/utils/adt/pseudotypes.c           |   1 +
 src/backend/utils/adt/varlena.c               |  42 ++++
 src/bin/pg_dump/pg_backup.h                   |   1 +
 src/bin/pg_dump/pg_dump.c                     |  36 +++-
 src/bin/pg_dump/pg_dump.h                     |   1 +
 src/bin/pg_dump/t/002_pg_dump.pl              |  12 +-
 src/bin/psql/describe.c                       |  28 ++-
 src/include/access/compressamapi.h            |  99 +++++++++
 src/include/access/detoast.h                  |   8 +
 src/include/access/toast_helper.h             |   1 +
 src/include/access/toast_internals.h          |  19 +-
 src/include/catalog/pg_am.dat                 |   6 +
 src/include/catalog/pg_am.h                   |   1 +
 src/include/catalog/pg_attribute.h            |   8 +-
 src/include/catalog/pg_proc.dat               |  20 ++
 src/include/catalog/pg_type.dat               |   5 +
 src/include/commands/defrem.h                 |   1 +
 src/include/executor/executor.h               |   4 +-
 src/include/nodes/execnodes.h                 |   6 +
 src/include/nodes/nodes.h                     |   1 +
 src/include/nodes/parsenodes.h                |   2 +
 src/include/parser/kwlist.h                   |   1 +
 src/include/pg_config.h.in                    |   3 +
 src/include/postgres.h                        |  12 +-
 src/test/regress/expected/compression.out     | 201 ++++++++++++++++++
 src/test/regress/expected/compression_1.out   | 189 ++++++++++++++++
 src/test/regress/expected/psql.out            |  68 +++---
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/serial_schedule              |   1 +
 src/test/regress/sql/compression.sql          |  85 ++++++++
 src/tools/msvc/Solution.pm                    |   1 +
 src/tools/pgindent/typedefs.list              |   1 +
 62 files changed, 1746 insertions(+), 123 deletions(-)
 create mode 100644 src/backend/access/compression/Makefile
 create mode 100644 src/backend/access/compression/compress_lz4.c
 create mode 100644 src/backend/access/compression/compress_pglz.c
 create mode 100644 src/include/access/compressamapi.h
 create mode 100644 src/test/regress/expected/compression.out
 create mode 100644 src/test/regress/expected/compression_1.out
 create mode 100644 src/test/regress/sql/compression.sql

diff --git a/configure b/configure
index ce9ea36999..0895b2f326 100755
--- a/configure
+++ b/configure
@@ -699,6 +699,7 @@ with_gnu_ld
 LD
 LDFLAGS_SL
 LDFLAGS_EX
+with_lz4
 with_zlib
 with_system_tzdata
 with_libxslt
@@ -864,6 +865,7 @@ with_libxml
 with_libxslt
 with_system_tzdata
 with_zlib
+with_lz4
 with_gnu_ld
 with_ssl
 with_openssl
@@ -1569,6 +1571,7 @@ Optional Packages:
   --with-system-tzdata=DIR
                           use system time zone data in DIR
   --without-zlib          do not use Zlib
+  --with-lz4              build with LZ4 support
   --with-gnu-ld           assume the C compiler uses GNU ld [default=no]
   --with-ssl=LIB          use LIB for SSL/TLS support (openssl)
   --with-openssl          obsolete spelling of --with-ssl=openssl
@@ -8563,6 +8566,39 @@ fi
 
 
 
+#
+# LZ4
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with LZ4 support" >&5
+$as_echo_n "checking whether to build with LZ4 support... " >&6; }
+
+
+
+# Check whether --with-lz4 was given.
+if test "${with_lz4+set}" = set; then :
+  withval=$with_lz4;
+  case $withval in
+    yes)
+
+$as_echo "#define USE_LZ4 1" >>confdefs.h
+
+      ;;
+    no)
+      :
+      ;;
+    *)
+      as_fn_error $? "no argument expected for --with-lz4 option" "$LINENO" 5
+      ;;
+  esac
+
+else
+  with_lz4=no
+
+fi
+
+
+
+
 #
 # Assignments
 #
@@ -12054,6 +12090,56 @@ fi
 
 fi
 
+if test "$with_lz4" = yes ; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for LZ4_compress in -llz4" >&5
+$as_echo_n "checking for LZ4_compress in -llz4... " >&6; }
+if ${ac_cv_lib_lz4_LZ4_compress+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-llz4  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char LZ4_compress ();
+int
+main ()
+{
+return LZ4_compress ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_lz4_LZ4_compress=yes
+else
+  ac_cv_lib_lz4_LZ4_compress=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_lz4_LZ4_compress" >&5
+$as_echo "$ac_cv_lib_lz4_LZ4_compress" >&6; }
+if test "x$ac_cv_lib_lz4_LZ4_compress" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBLZ4 1
+_ACEOF
+
+  LIBS="-llz4 $LIBS"
+
+else
+  as_fn_error $? "library 'lz4' is required for LZ4 support" "$LINENO" 5
+fi
+
+fi
+
 if test "$enable_spinlocks" = yes; then
 
 $as_echo "#define HAVE_SPINLOCKS 1" >>confdefs.h
@@ -13320,6 +13406,36 @@ Use --without-zlib to disable zlib support." "$LINENO" 5
 fi
 
 
+fi
+
+if test "$with_lz4" = yes ; then
+  for ac_header in lz4/lz4.h
+do :
+  ac_fn_c_check_header_mongrel "$LINENO" "lz4/lz4.h" "ac_cv_header_lz4_lz4_h" "$ac_includes_default"
+if test "x$ac_cv_header_lz4_lz4_h" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LZ4_LZ4_H 1
+_ACEOF
+
+else
+  for ac_header in lz4.h
+do :
+  ac_fn_c_check_header_mongrel "$LINENO" "lz4.h" "ac_cv_header_lz4_h" "$ac_includes_default"
+if test "x$ac_cv_header_lz4_h" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LZ4_H 1
+_ACEOF
+
+else
+  as_fn_error $? "lz4.h header file is required for LZ4" "$LINENO" 5
+fi
+
+done
+
+fi
+
+done
+
 fi
 
 if test "$with_gssapi" = yes ; then
diff --git a/configure.ac b/configure.ac
index 07da84d401..63940b78a0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -986,6 +986,14 @@ PGAC_ARG_BOOL(with, zlib, yes,
               [do not use Zlib])
 AC_SUBST(with_zlib)
 
+#
+# LZ4
+#
+AC_MSG_CHECKING([whether to build with LZ4 support])
+PGAC_ARG_BOOL(with, lz4, no, [build with LZ4 support],
+              [AC_DEFINE([USE_LZ4], 1, [Define to 1 to build with LZ4 support. (--with-lz4)])])
+AC_SUBST(with_lz4)
+
 #
 # Assignments
 #
@@ -1173,6 +1181,10 @@ failure.  It is possible the compiler isn't looking in the proper directory.
 Use --without-zlib to disable zlib support.])])
 fi
 
+if test "$with_lz4" = yes ; then
+  AC_CHECK_LIB(lz4, LZ4_compress, [], [AC_MSG_ERROR([library 'lz4' is required for LZ4 support])])
+fi
+
 if test "$enable_spinlocks" = yes; then
   AC_DEFINE(HAVE_SPINLOCKS, 1, [Define to 1 if you have spinlocks.])
 else
@@ -1406,6 +1418,11 @@ failure.  It is possible the compiler isn't looking in the proper directory.
 Use --without-zlib to disable zlib support.])])
 fi
 
+if test "$with_lz4" = yes ; then
+  AC_CHECK_HEADERS(lz4/lz4.h, [],
+	[AC_CHECK_HEADERS(lz4.h, [], [AC_MSG_ERROR([lz4.h header file is required for LZ4])])])
+fi
+
 if test "$with_gssapi" = yes ; then
   AC_CHECK_HEADERS(gssapi/gssapi.h, [],
 	[AC_CHECK_HEADERS(gssapi.h, [], [AC_MSG_ERROR([gssapi.h header file is required for GSSAPI])])])
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 1e9a4625cc..4d7ed698b9 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -3762,6 +3762,9 @@ CREATE TABLE measurement (
        <productname>PostgreSQL</productname>
        tables (or, possibly, foreign tables).  It is possible to specify a
        tablespace and storage parameters for each partition separately.
+       By default, each column in a partition inherits the compression method
+       from parent table's column, however a different compression method can be
+       set for each partition.
       </para>
 
       <para>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 569f4c9da7..51a7a977a5 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -69,6 +69,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] |
   UNIQUE <replaceable class="parameter">index_parameters</replaceable> |
   PRIMARY KEY <replaceable class="parameter">index_parameters</replaceable> |
+  COMPRESSION <replaceable class="parameter">compression_method</replaceable> |
   REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -605,6 +606,17 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
         </listitem>
        </varlistentry>
 
+       <varlistentry>
+        <term><literal>INCLUDING COMPRESSION</literal></term>
+        <listitem>
+         <para>
+          Compression method of the columns will be copied.  The default
+          behavior is to exclude compression methods, resulting in the columns
+          having the default compression method.
+         </para>
+        </listitem>
+       </varlistentry>
+
        <varlistentry>
         <term><literal>INCLUDING CONSTRAINTS</literal></term>
         <listitem>
@@ -981,6 +993,19 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>COMPRESSION <replaceable class="parameter">compression_method</replaceable></literal></term>
+    <listitem>
+     <para>
+      This sets the compression method for a column.  The supported compression
+      methods are <literal>pglz</literal> and <literal>lz4</literal>.
+      <literal>lz4</literal> is available only if <literal>--with-lz4</literal>
+      was used when building <productname>PostgreSQL</productname>. The default
+      is <literal>pglz</literal>.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="sql-createtable-exclude">
     <term><literal>EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ]</literal></term>
     <listitem>
diff --git a/src/backend/access/Makefile b/src/backend/access/Makefile
index 0880e0a8bb..ba08bdd63e 100644
--- a/src/backend/access/Makefile
+++ b/src/backend/access/Makefile
@@ -8,7 +8,7 @@ subdir = src/backend/access
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
-SUBDIRS	    = brin common gin gist hash heap index nbtree rmgrdesc spgist \
+SUBDIRS	    = brin common compression gin gist hash heap index nbtree rmgrdesc spgist \
 			  table tablesample transam
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/brin/brin_tuple.c b/src/backend/access/brin/brin_tuple.c
index a7eb1c9473..0ab5712c71 100644
--- a/src/backend/access/brin/brin_tuple.c
+++ b/src/backend/access/brin/brin_tuple.c
@@ -213,7 +213,10 @@ brin_form_tuple(BrinDesc *brdesc, BlockNumber blkno, BrinMemTuple *tuple,
 				(atttype->typstorage == TYPSTORAGE_EXTENDED ||
 				 atttype->typstorage == TYPSTORAGE_MAIN))
 			{
-				Datum		cvalue = toast_compress_datum(value);
+				Form_pg_attribute att = TupleDescAttr(brdesc->bd_tupdesc,
+													  keyno);
+				Datum		cvalue = toast_compress_datum(value,
+														  att->attcompression);
 
 				if (DatumGetPointer(cvalue) != NULL)
 				{
diff --git a/src/backend/access/common/detoast.c b/src/backend/access/common/detoast.c
index d1cdbaf648..b78d49167b 100644
--- a/src/backend/access/common/detoast.c
+++ b/src/backend/access/common/detoast.c
@@ -13,6 +13,7 @@
 
 #include "postgres.h"
 
+#include "access/compressamapi.h"
 #include "access/detoast.h"
 #include "access/table.h"
 #include "access/tableam.h"
@@ -457,28 +458,78 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
 }
 
 /* ----------
- * toast_decompress_datum -
+ * toast_get_compression_oid -
  *
- * Decompress a compressed version of a varlena datum
+ * Returns the Oid of the compression method stored in the compressed data.  If
+ * the varlena is not compressed then returns InvalidOid.
  */
-static struct varlena *
-toast_decompress_datum(struct varlena *attr)
+Oid
+toast_get_compression_oid(struct varlena *attr)
 {
-	struct varlena *result;
+	if (VARATT_IS_EXTERNAL_ONDISK(attr))
+	{
+		struct varatt_external toast_pointer;
+
+		VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr);
+
+		/* fast path for non-compressed external datums */
+		if (!VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
+			return InvalidOid;
+
+		/*
+		 * Just fetch the toast compress header to know the compression method
+		 * in the compressed data.
+		 */
+		attr = toast_fetch_datum_slice(attr, 0, VARHDRSZ_COMPRESS);
+	}
+	else if (!VARATT_IS_COMPRESSED(attr))
+		return InvalidOid;
+
+	return CompressionIdToOid(TOAST_COMPRESS_METHOD(attr));
+}
+
+/* ----------
+ * toast_get_compression_handler - get the compression handler routines
+ *
+ * helper function for toast_decompress_datum and toast_decompress_datum_slice
+ */
+static inline const CompressionAmRoutine *
+toast_get_compression_handler(struct varlena *attr)
+{
+	const CompressionAmRoutine *cmroutine;
+	CompressionId cmid;
 
 	Assert(VARATT_IS_COMPRESSED(attr));
 
-	result = (struct varlena *)
-		palloc(TOAST_COMPRESS_RAWSIZE(attr) + VARHDRSZ);
-	SET_VARSIZE(result, TOAST_COMPRESS_RAWSIZE(attr) + VARHDRSZ);
+	cmid = TOAST_COMPRESS_METHOD(attr);
 
-	if (pglz_decompress(TOAST_COMPRESS_RAWDATA(attr),
-						TOAST_COMPRESS_SIZE(attr),
-						VARDATA(result),
-						TOAST_COMPRESS_RAWSIZE(attr), true) < 0)
-		elog(ERROR, "compressed data is corrupted");
+	/* Get the handler routines for the compression method */
+	switch (cmid)
+	{
+		case PGLZ_COMPRESSION_ID:
+			cmroutine = &pglz_compress_methods;
+			break;
+		case LZ4_COMPRESSION_ID:
+			cmroutine = &lz4_compress_methods;
+			break;
+		default:
+			elog(ERROR, "invalid compression method id %d", cmid);
+	}
 
-	return result;
+	return cmroutine;
+}
+
+/* ----------
+ * toast_decompress_datum -
+ *
+ * Decompress a compressed version of a varlena datum
+ */
+static struct varlena *
+toast_decompress_datum(struct varlena *attr)
+{
+	const CompressionAmRoutine *cmroutine = toast_get_compression_handler(attr);
+
+	return cmroutine->datum_decompress(attr);
 }
 
 
@@ -492,22 +543,16 @@ toast_decompress_datum(struct varlena *attr)
 static struct varlena *
 toast_decompress_datum_slice(struct varlena *attr, int32 slicelength)
 {
-	struct varlena *result;
-	int32		rawsize;
-
-	Assert(VARATT_IS_COMPRESSED(attr));
-
-	result = (struct varlena *) palloc(slicelength + VARHDRSZ);
-
-	rawsize = pglz_decompress(TOAST_COMPRESS_RAWDATA(attr),
-							  VARSIZE(attr) - TOAST_COMPRESS_HDRSZ,
-							  VARDATA(result),
-							  slicelength, false);
-	if (rawsize < 0)
-		elog(ERROR, "compressed data is corrupted");
+	const CompressionAmRoutine *cmroutine = toast_get_compression_handler(attr);
 
-	SET_VARSIZE(result, rawsize + VARHDRSZ);
-	return result;
+	/*
+	 * If the handler supports the slice decompression then decompress the
+	 * slice otherwise decompress complete data.
+	 */
+	if (cmroutine->datum_decompress_slice)
+		return cmroutine->datum_decompress_slice(attr, slicelength);
+	else
+		return cmroutine->datum_decompress(attr);
 }
 
 /* ----------
diff --git a/src/backend/access/common/indextuple.c b/src/backend/access/common/indextuple.c
index b72a138497..1d43d5d2ff 100644
--- a/src/backend/access/common/indextuple.c
+++ b/src/backend/access/common/indextuple.c
@@ -16,6 +16,7 @@
 
 #include "postgres.h"
 
+#include "access/compressamapi.h"
 #include "access/detoast.h"
 #include "access/heaptoast.h"
 #include "access/htup_details.h"
@@ -103,7 +104,8 @@ index_form_tuple(TupleDesc tupleDescriptor,
 			(att->attstorage == TYPSTORAGE_EXTENDED ||
 			 att->attstorage == TYPSTORAGE_MAIN))
 		{
-			Datum		cvalue = toast_compress_datum(untoasted_values[i]);
+			Datum		cvalue = toast_compress_datum(untoasted_values[i],
+													  att->attcompression);
 
 			if (DatumGetPointer(cvalue) != NULL)
 			{
diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c
index 9b9da0f41b..b04c5a5eb8 100644
--- a/src/backend/access/common/toast_internals.c
+++ b/src/backend/access/common/toast_internals.c
@@ -44,46 +44,51 @@ static bool toastid_valueid_exists(Oid toastrelid, Oid valueid);
  * ----------
  */
 Datum
-toast_compress_datum(Datum value)
+toast_compress_datum(Datum value, Oid cmoid)
 {
-	struct varlena *tmp;
-	int32		valsize = VARSIZE_ANY_EXHDR(DatumGetPointer(value));
-	int32		len;
+	struct varlena *tmp = NULL;
+	int32		valsize;
+	const CompressionAmRoutine *cmroutine = NULL;
 
 	Assert(!VARATT_IS_EXTERNAL(DatumGetPointer(value)));
 	Assert(!VARATT_IS_COMPRESSED(DatumGetPointer(value)));
 
-	/*
-	 * No point in wasting a palloc cycle if value size is out of the allowed
-	 * range for compression
-	 */
-	if (valsize < PGLZ_strategy_default->min_input_size ||
-		valsize > PGLZ_strategy_default->max_input_size)
-		return PointerGetDatum(NULL);
+	Assert(OidIsValid(cmoid));
+
+	/* Get the handler routines for the compression method */
+	switch (cmoid)
+	{
+		case PGLZ_COMPRESSION_AM_OID:
+			cmroutine = &pglz_compress_methods;
+			break;
+		case LZ4_COMPRESSION_AM_OID:
+			cmroutine = &lz4_compress_methods;
+			break;
+		default:
+			elog(ERROR, "Invalid compression method oid %u", cmoid);
+	}
 
-	tmp = (struct varlena *) palloc(PGLZ_MAX_OUTPUT(valsize) +
-									TOAST_COMPRESS_HDRSZ);
+	/* Call the actual compression function */
+	tmp = cmroutine->datum_compress((const struct varlena *) value);
+	if (!tmp)
+		return PointerGetDatum(NULL);
 
 	/*
-	 * We recheck the actual size even if pglz_compress() reports success,
-	 * because it might be satisfied with having saved as little as one byte
-	 * in the compressed data --- which could turn into a net loss once you
-	 * consider header and alignment padding.  Worst case, the compressed
-	 * format might require three padding bytes (plus header, which is
-	 * included in VARSIZE(tmp)), whereas the uncompressed format would take
-	 * only one header byte and no padding if the value is short enough.  So
-	 * we insist on a savings of more than 2 bytes to ensure we have a gain.
+	 * We recheck the actual size even if compression reports success, because
+	 * it might be satisfied with having saved as little as one byte in the
+	 * compressed data --- which could turn into a net loss once you consider
+	 * header and alignment padding.  Worst case, the compressed format might
+	 * require three padding bytes (plus header, which is included in
+	 * VARSIZE(tmp)), whereas the uncompressed format would take only one
+	 * header byte and no padding if the value is short enough.  So we insist
+	 * on a savings of more than 2 bytes to ensure we have a gain.
 	 */
-	len = pglz_compress(VARDATA_ANY(DatumGetPointer(value)),
-						valsize,
-						TOAST_COMPRESS_RAWDATA(tmp),
-						PGLZ_strategy_default);
-	if (len >= 0 &&
-		len + TOAST_COMPRESS_HDRSZ < valsize - 2)
+	valsize = VARSIZE_ANY_EXHDR(DatumGetPointer(value));
+
+	if (VARSIZE(tmp) < valsize - 2)
 	{
-		TOAST_COMPRESS_SET_RAWSIZE(tmp, valsize);
-		SET_VARSIZE_COMPRESSED(tmp, len + TOAST_COMPRESS_HDRSZ);
 		/* successful compression */
+		TOAST_COMPRESS_SET_SIZE_AND_METHOD(tmp, valsize, CompressionOidToId(cmoid));
 		return PointerGetDatum(tmp);
 	}
 	else
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 902f59440c..ca26fab487 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -19,6 +19,7 @@
 
 #include "postgres.h"
 
+#include "access/compressamapi.h"
 #include "access/htup_details.h"
 #include "access/tupdesc_details.h"
 #include "catalog/pg_collation.h"
@@ -470,6 +471,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			return false;
 		if (attr1->attcollation != attr2->attcollation)
 			return false;
+		if (attr1->attcompression != attr2->attcompression)
+			return false;
 		/* attacl, attoptions and attfdwoptions are not even present... */
 	}
 
@@ -664,6 +667,11 @@ TupleDescInitEntry(TupleDesc desc,
 	att->attstorage = typeForm->typstorage;
 	att->attcollation = typeForm->typcollation;
 
+	if (IsStorageCompressible(typeForm->typstorage))
+		att->attcompression = DefaultCompressionOid;
+	else
+		att->attcompression = InvalidOid;
+
 	ReleaseSysCache(tuple);
 }
 
diff --git a/src/backend/access/compression/Makefile b/src/backend/access/compression/Makefile
new file mode 100644
index 0000000000..779f54e785
--- /dev/null
+++ b/src/backend/access/compression/Makefile
@@ -0,0 +1,17 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for access/compression
+#
+# IDENTIFICATION
+#    src/backend/access/compression/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/access/compression
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = compress_pglz.o compress_lz4.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/compression/compress_lz4.c b/src/backend/access/compression/compress_lz4.c
new file mode 100644
index 0000000000..1856cf7df7
--- /dev/null
+++ b/src/backend/access/compression/compress_lz4.c
@@ -0,0 +1,164 @@
+/*-------------------------------------------------------------------------
+ *
+ * compress_lz4.c
+ *		lz4 compression method.
+ *
+ * Portions Copyright (c) 2021, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/compression/compress_lz4.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#ifdef HAVE_LIBLZ4
+#include <lz4.h>
+#endif
+
+#include "access/compressamapi.h"
+#include "fmgr.h"
+#include "utils/builtins.h"
+
+/*
+ * lz4_cmcompress - compression routine for lz4 compression method
+ *
+ * Compresses source into dest using the default strategy. Returns the
+ * compressed varlena, or NULL if compression fails.
+ */
+static struct varlena *
+lz4_cmcompress(const struct varlena *value)
+{
+#ifndef HAVE_LIBLZ4
+	ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("not built with lz4 support")));
+#else
+	int32		valsize;
+	int32		len;
+	int32		max_size;
+	struct varlena *tmp = NULL;
+
+	valsize = VARSIZE_ANY_EXHDR(value);
+
+	/*
+	 * Get maximum size of the compressed data that lz4 compression may output
+	 * and allocate the memory for holding the compressed data and the header.
+	 */
+	max_size = LZ4_compressBound(valsize);
+	tmp = (struct varlena *) palloc(max_size + VARHDRSZ_COMPRESS);
+
+	len = LZ4_compress_default(VARDATA_ANY(value),
+							   (char *) tmp + VARHDRSZ_COMPRESS,
+							   valsize, max_size);
+	if (len <= 0)
+		elog(ERROR, "could not compress data with lz4");
+
+	/* data is incompressible so just free the memory and return NULL */
+	if (len > valsize)
+	{
+		pfree(tmp);
+		return NULL;
+	}
+
+	SET_VARSIZE_COMPRESSED(tmp, len + VARHDRSZ_COMPRESS);
+
+	return tmp;
+#endif
+}
+
+/*
+ * lz4_cmdecompress - decompression routine for lz4 compression method
+ *
+ * Returns the decompressed varlena.
+ */
+static struct varlena *
+lz4_cmdecompress(const struct varlena *value)
+{
+#ifndef HAVE_LIBLZ4
+	ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("not built with lz4 support")));
+#else
+	int32		rawsize;
+	struct varlena *result;
+
+	/* allocate memory for holding the uncompressed data */
+	result = (struct varlena *) palloc(VARRAWSIZE_4B_C(value) + VARHDRSZ);
+
+	/* decompress data using lz4 routine */
+	rawsize = LZ4_decompress_safe((char *) value + VARHDRSZ_COMPRESS,
+								  VARDATA(result),
+								  VARSIZE(value) - VARHDRSZ_COMPRESS,
+								  VARRAWSIZE_4B_C(value));
+	if (rawsize < 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg_internal("compressed lz4 data is corrupt")));
+
+
+	SET_VARSIZE(result, rawsize + VARHDRSZ);
+
+	return result;
+#endif
+}
+
+/*
+ * lz4_cmdecompress_slice - slice decompression routine for lz4 compression
+ *
+ * Decompresses part of the data. Returns the decompressed varlena.
+ */
+static struct varlena *
+lz4_cmdecompress_slice(const struct varlena *value, int32 slicelength)
+{
+#ifndef HAVE_LIBLZ4
+	ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("not built with lz4 support")));
+#else
+	int32		rawsize;
+	struct varlena *result;
+
+	/* allocate memory for holding the uncompressed data */
+	result = (struct varlena *) palloc(VARRAWSIZE_4B_C(value) + VARHDRSZ);
+
+	/* decompress partial data using lz4 routine */
+	rawsize = LZ4_decompress_safe_partial((char *) value + VARHDRSZ_COMPRESS,
+										  VARDATA(result),
+										  VARSIZE(value) - VARHDRSZ_COMPRESS,
+										  slicelength,
+										  VARRAWSIZE_4B_C(value));
+	if (rawsize < 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg_internal("compressed lz4 data is corrupt")));
+
+	SET_VARSIZE(result, rawsize + VARHDRSZ);
+
+	return result;
+#endif
+}
+
+/* ------------------------------------------------------------------------
+ * Definition of the lz4 compression access method.
+ * ------------------------------------------------------------------------
+ */
+const CompressionAmRoutine lz4_compress_methods = {
+	.type = T_CompressionAmRoutine,
+	.datum_compress = lz4_cmcompress,
+	.datum_decompress = lz4_cmdecompress,
+	.datum_decompress_slice = lz4_cmdecompress_slice
+};
+
+/* lz4 compression handler function */
+Datum
+lz4handler(PG_FUNCTION_ARGS)
+{
+#ifndef HAVE_LIBLZ4
+	ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("not built with lz4 support")));
+#else
+	PG_RETURN_POINTER(&lz4_compress_methods);
+#endif
+}
diff --git a/src/backend/access/compression/compress_pglz.c b/src/backend/access/compression/compress_pglz.c
new file mode 100644
index 0000000000..8a4bf427cf
--- /dev/null
+++ b/src/backend/access/compression/compress_pglz.c
@@ -0,0 +1,138 @@
+/*-------------------------------------------------------------------------
+ *
+ * compress_pglz.c
+ *	  pglz compression method
+ *
+ * Portions Copyright (c) 2021, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/compression/compress_pglz.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/compressamapi.h"
+#include "common/pg_lzcompress.h"
+
+#include "fmgr.h"
+#include "utils/builtins.h"
+
+/*
+ * pglz_cmcompress - compression routine for pglz compression method
+ *
+ * Compresses source into dest using the default strategy. Returns the
+ * compressed varlena, or NULL if compression fails.
+ */
+static struct varlena *
+pglz_cmcompress(const struct varlena *value)
+{
+	int32		valsize,
+				len;
+	struct varlena *tmp = NULL;
+
+	valsize = VARSIZE_ANY_EXHDR(DatumGetPointer(value));
+
+	/*
+	 * No point in wasting a palloc cycle if value size is outside the allowed
+	 * range for compression.
+	 */
+	if (valsize < PGLZ_strategy_default->min_input_size ||
+		valsize > PGLZ_strategy_default->max_input_size)
+		return NULL;
+
+	/*
+	 * Get maximum size of the compressed data that pglz compression may output
+	 * and allocate the memory for holding the compressed data and the header.
+	 */
+	tmp = (struct varlena *) palloc(PGLZ_MAX_OUTPUT(valsize) +
+									VARHDRSZ_COMPRESS);
+
+	len = pglz_compress(VARDATA_ANY(value),
+						valsize,
+						(char *) tmp + VARHDRSZ_COMPRESS,
+						NULL);
+	if (len < 0)
+	{
+		pfree(tmp);
+		return NULL;
+	}
+
+	SET_VARSIZE_COMPRESSED(tmp, len + VARHDRSZ_COMPRESS);
+
+	return tmp;
+}
+
+/*
+ * pglz_cmdecompress - decompression routine for pglz compression method
+ *
+ * Returns the decompressed varlena.
+ */
+static struct varlena *
+pglz_cmdecompress(const struct varlena *value)
+{
+	struct varlena *result;
+	int32		rawsize;
+
+	result = (struct varlena *) palloc(VARRAWSIZE_4B_C(value) + VARHDRSZ);
+
+	rawsize = pglz_decompress((char *) value + VARHDRSZ_COMPRESS,
+							  VARSIZE(value) - VARHDRSZ_COMPRESS,
+							  VARDATA(result),
+							  VARRAWSIZE_4B_C(value), true);
+	if (rawsize < 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg_internal("compressed pglz data is corrupt")));
+
+	SET_VARSIZE(result, rawsize + VARHDRSZ);
+
+	return result;
+}
+
+/*
+ * pglz_decompress - slice decompression routine for pglz compression method
+ *
+ * Decompresses part of the data. Returns the decompressed varlena.
+ */
+static struct varlena *
+pglz_cmdecompress_slice(const struct varlena *value,
+						int32 slicelength)
+{
+	struct varlena *result;
+	int32		rawsize;
+
+	result = (struct varlena *) palloc(slicelength + VARHDRSZ);
+
+	rawsize = pglz_decompress((char *) value + VARHDRSZ_COMPRESS,
+							  VARSIZE(value) - VARHDRSZ_COMPRESS,
+							  VARDATA(result),
+							  slicelength, false);
+	if (rawsize < 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg_internal("compressed pglz data is corrupt")));
+
+	SET_VARSIZE(result, rawsize + VARHDRSZ);
+
+	return result;
+}
+
+/* ------------------------------------------------------------------------
+ * Definition of the pglz compression access method.
+ * ------------------------------------------------------------------------
+ */
+const CompressionAmRoutine pglz_compress_methods = {
+	.type = T_CompressionAmRoutine,
+	.datum_compress = pglz_cmcompress,
+	.datum_decompress = pglz_cmdecompress,
+	.datum_decompress_slice = pglz_cmdecompress_slice
+};
+
+/* pglz compression handler function */
+Datum
+pglzhandler(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_POINTER(&pglz_compress_methods);
+}
diff --git a/src/backend/access/table/toast_helper.c b/src/backend/access/table/toast_helper.c
index fb36151ce5..53f78f9c3e 100644
--- a/src/backend/access/table/toast_helper.c
+++ b/src/backend/access/table/toast_helper.c
@@ -54,6 +54,7 @@ toast_tuple_init(ToastTupleContext *ttc)
 
 		ttc->ttc_attr[i].tai_colflags = 0;
 		ttc->ttc_attr[i].tai_oldexternal = NULL;
+		ttc->ttc_attr[i].tai_compression = att->attcompression;
 
 		if (ttc->ttc_oldvalues != NULL)
 		{
@@ -226,9 +227,11 @@ void
 toast_tuple_try_compression(ToastTupleContext *ttc, int attribute)
 {
 	Datum	   *value = &ttc->ttc_values[attribute];
-	Datum		new_value = toast_compress_datum(*value);
+	Datum		new_value;
 	ToastAttrInfo *attr = &ttc->ttc_attr[attribute];
 
+	new_value = toast_compress_datum(*value, attr->tai_compression);
+
 	if (DatumGetPointer(new_value) != NULL)
 	{
 		/* successful compression */
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 6f615e6622..9b451eaa71 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -17,6 +17,7 @@
 #include <unistd.h>
 #include <signal.h>
 
+#include "access/compressamapi.h"
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
@@ -731,6 +732,10 @@ DefineAttr(char *name, char *type, int attnum, int nullness)
 	attrtypes[attnum]->attcacheoff = -1;
 	attrtypes[attnum]->atttypmod = -1;
 	attrtypes[attnum]->attislocal = true;
+	if (IsStorageCompressible(attrtypes[attnum]->attstorage))
+		attrtypes[attnum]->attcompression = DefaultCompressionOid;
+	else
+		attrtypes[attnum]->attcompression = InvalidOid;
 
 	if (nullness == BOOTCOL_NULL_FORCE_NOT_NULL)
 	{
diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl
index b159958112..f5bb37b972 100644
--- a/src/backend/catalog/genbki.pl
+++ b/src/backend/catalog/genbki.pl
@@ -187,7 +187,9 @@ my $GenbkiNextOid = $FirstGenbkiObjectId;
 my $C_COLLATION_OID =
   Catalog::FindDefinedSymbolFromData($catalog_data{pg_collation},
 	'C_COLLATION_OID');
-
+my $PGLZ_COMPRESSION_AM_OID =
+  Catalog::FindDefinedSymbolFromData($catalog_data{pg_am},
+	'PGLZ_COMPRESSION_AM_OID');
 
 # Fill in pg_class.relnatts by looking at the referenced catalog's schema.
 # This is ugly but there's no better place; Catalog::AddDefaultValues
@@ -906,6 +908,9 @@ sub morph_row_for_pgattr
 	$row->{attcollation} =
 	  $type->{typcollation} ne '0' ? $C_COLLATION_OID : 0;
 
+	$row->{attcompression} =
+	  $type->{typstorage} ne 'p' && $type->{typstorage} ne 'e' ? $PGLZ_COMPRESSION_AM_OID : 0;
+
 	if (defined $attr->{forcenotnull})
 	{
 		$row->{attnotnull} = 't';
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 9abc4a1f55..b53b6b50e6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -29,6 +29,7 @@
  */
 #include "postgres.h"
 
+#include "access/compressamapi.h"
 #include "access/genam.h"
 #include "access/htup_details.h"
 #include "access/multixact.h"
@@ -789,6 +790,7 @@ InsertPgAttributeTuples(Relation pg_attribute_rel,
 		slot[slotCount]->tts_values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(attrs->attislocal);
 		slot[slotCount]->tts_values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(attrs->attinhcount);
 		slot[slotCount]->tts_values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(attrs->attcollation);
+		slot[slotCount]->tts_values[Anum_pg_attribute_attcompression - 1] = CharGetDatum(attrs->attcompression);
 		if (attoptions && attoptions[natts] != (Datum) 0)
 			slot[slotCount]->tts_values[Anum_pg_attribute_attoptions - 1] = attoptions[natts];
 		else
@@ -1715,6 +1717,8 @@ RemoveAttributeById(Oid relid, AttrNumber attnum)
 		/* Unset this so no one tries to look up the generation expression */
 		attStruct->attgenerated = '\0';
 
+		attStruct->attcompression = InvalidOid;
+
 		/*
 		 * Change the column name to something that isn't likely to conflict
 		 */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 1514937748..d1d6bdf470 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -24,6 +24,7 @@
 #include <unistd.h>
 
 #include "access/amapi.h"
+#include "access/compressamapi.h"
 #include "access/heapam.h"
 #include "access/multixact.h"
 #include "access/reloptions.h"
@@ -348,6 +349,7 @@ ConstructTupleDescriptor(Relation heapRelation,
 			to->attbyval = from->attbyval;
 			to->attstorage = from->attstorage;
 			to->attalign = from->attalign;
+			to->attcompression = from->attcompression;
 		}
 		else
 		{
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index d7b806020d..a549481557 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -220,6 +220,11 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 	TupleDescAttr(tupdesc, 1)->attstorage = TYPSTORAGE_PLAIN;
 	TupleDescAttr(tupdesc, 2)->attstorage = TYPSTORAGE_PLAIN;
 
+	/* Toast field should not be compressed */
+	TupleDescAttr(tupdesc, 0)->attcompression = InvalidOid;
+	TupleDescAttr(tupdesc, 1)->attcompression = InvalidOid;
+	TupleDescAttr(tupdesc, 2)->attcompression = InvalidOid;
+
 	/*
 	 * Toast tables for regular relations go in pg_toast; those for temp
 	 * relations go into the per-backend temp-toast-table namespace.
diff --git a/src/backend/commands/amcmds.c b/src/backend/commands/amcmds.c
index eff9535ed0..3ad4a61739 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -175,6 +175,16 @@ get_table_am_oid(const char *amname, bool missing_ok)
 	return get_am_type_oid(amname, AMTYPE_TABLE, missing_ok);
 }
 
+/*
+ * get_compression_am_oid - given an access method name, look up its OID
+ *		and verify it corresponds to an compression AM.
+ */
+Oid
+get_compression_am_oid(const char *amname, bool missing_ok)
+{
+	return get_am_type_oid(amname, AMTYPE_COMPRESSION, missing_ok);
+}
+
 /*
  * get_am_oid - given an access method name, look up its OID.
  *		The type is not checked.
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index dce882012e..1d17dc0d6b 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -61,6 +61,7 @@ typedef struct
 	CommandId	output_cid;		/* cmin to insert in output tuples */
 	int			ti_options;		/* table_tuple_insert performance options */
 	BulkInsertState bistate;	/* bulk insert state */
+	TupleTableSlot *decompress_tuple_slot;	/* to hold the decompress tuple */
 } DR_intorel;
 
 /* utility functions for CTAS definition creation */
@@ -581,6 +582,15 @@ intorel_receive(TupleTableSlot *slot, DestReceiver *self)
 	/* Nothing to insert if WITH NO DATA is specified. */
 	if (!myState->into->skipData)
 	{
+		/*
+		 * Compare the compression method of the compressed attribute in the
+		 * source tuple with target attribute and if those are different then
+		 * decompress those attributes.
+		 */
+		slot = CompareCompressionMethodAndDecompress(slot,
+													 &myState->decompress_tuple_slot,
+													 myState->rel->rd_att);
+
 		/*
 		 * Note that the input slot might not be of the type of the target
 		 * relation. That's supported by table_tuple_insert(), but slightly
@@ -619,6 +629,10 @@ intorel_shutdown(DestReceiver *self)
 	/* close rel, but keep lock until commit */
 	table_close(myState->rel, NoLock);
 	myState->rel = NULL;
+
+	/* release the slot used for decompressing the tuple */
+	if (myState->decompress_tuple_slot)
+		ExecDropSingleTupleTableSlot(myState->decompress_tuple_slot);
 }
 
 /*
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index c5c25ce11d..713fc3fceb 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -56,6 +56,7 @@ typedef struct
 	CommandId	output_cid;		/* cmin to insert in output tuples */
 	int			ti_options;		/* table_tuple_insert performance options */
 	BulkInsertState bistate;	/* bulk insert state */
+	TupleTableSlot *decompress_tuple_slot;	/* to hold the decompress tuple */
 } DR_transientrel;
 
 static int	matview_maintenance_depth = 0;
@@ -486,6 +487,15 @@ transientrel_receive(TupleTableSlot *slot, DestReceiver *self)
 {
 	DR_transientrel *myState = (DR_transientrel *) self;
 
+	/*
+	 * Compare the compression method of the compressed attribute in the
+	 * source tuple with target attribute and if those are different then
+	 * decompress those attributes.
+	 */
+	slot = CompareCompressionMethodAndDecompress(slot,
+												 &myState->decompress_tuple_slot,
+												 myState->transientrel->rd_att);
+
 	/*
 	 * Note that the input slot might not be of the type of the target
 	 * relation. That's supported by table_tuple_insert(), but slightly less
@@ -521,6 +531,10 @@ transientrel_shutdown(DestReceiver *self)
 	/* close transientrel, but keep lock until commit */
 	table_close(myState->transientrel, NoLock);
 	myState->transientrel = NULL;
+
+	/* release the slot used for decompressing the tuple */
+	if (myState->decompress_tuple_slot)
+		ExecDropSingleTupleTableSlot(myState->decompress_tuple_slot);
 }
 
 /*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 420991e315..dd81d5bf4e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -15,6 +15,7 @@
 #include "postgres.h"
 
 #include "access/attmap.h"
+#include "access/compressamapi.h"
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/heapam_xlog.h"
@@ -559,7 +560,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static void ATExecAlterCollationRefreshVersion(Relation rel, List *coll);
-
+static Oid GetAttributeCompression(Form_pg_attribute att, char *compression);
 
 /* ----------------------------------------------------------------
  *		DefineRelation
@@ -853,6 +854,18 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 
 		if (colDef->generated)
 			attr->attgenerated = colDef->generated;
+
+		/*
+		 * lookup attribute's compression method and store its Oid in the
+		 * attr->attcompression.
+		 */
+		if (relkind == RELKIND_RELATION ||
+			relkind == RELKIND_PARTITIONED_TABLE ||
+			relkind == RELKIND_MATVIEW)
+			attr->attcompression =
+				GetAttributeCompression(attr, colDef->compression);
+		else
+			attr->attcompression = InvalidOid;
 	}
 
 	/*
@@ -2397,6 +2410,21 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 									   storage_name(def->storage),
 									   storage_name(attribute->attstorage))));
 
+				/* Copy/check compression parameter */
+				if (OidIsValid(attribute->attcompression))
+				{
+					char *compression = get_am_name(attribute->attcompression);
+
+					if (!def->compression)
+						def->compression = compression;
+					else if (strcmp(def->compression, compression) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_DATATYPE_MISMATCH),
+								 errmsg("column \"%s\" has a compression method conflict",
+										attributeName),
+								 errdetail("%s versus %s", def->compression, compression)));
+				}
+
 				def->inhcount++;
 				/* Merge of NOT NULL constraints = OR 'em together */
 				def->is_not_null |= attribute->attnotnull;
@@ -2431,6 +2459,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				def->collOid = attribute->attcollation;
 				def->constraints = NIL;
 				def->location = -1;
+				def->compression = get_am_name(attribute->attcompression);
 				inhSchema = lappend(inhSchema, def);
 				newattmap->attnums[parent_attno - 1] = ++child_attno;
 			}
@@ -2676,6 +2705,19 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 									   storage_name(def->storage),
 									   storage_name(newdef->storage))));
 
+				/* Copy compression parameter */
+				if (!def->compression)
+					def->compression = newdef->compression;
+				else if (newdef->compression)
+				{
+					if (strcmp(def->compression, newdef->compression))
+						ereport(ERROR,
+								(errcode(ERRCODE_DATATYPE_MISMATCH),
+								 errmsg("column \"%s\" has a compression method conflict",
+										attributeName),
+								 errdetail("%s versus %s", def->compression, newdef->compression)));
+				}
+
 				/* Mark the column as locally defined */
 				def->is_local = true;
 				/* Merge of NOT NULL constraints = OR 'em together */
@@ -6341,6 +6383,18 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	attribute.attislocal = colDef->is_local;
 	attribute.attinhcount = colDef->inhcount;
 	attribute.attcollation = collOid;
+
+	/*
+	 * lookup attribute's compression method and store its Oid in the
+	 * attr->attcompression.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		attribute.attcompression = GetAttributeCompression(&attribute,
+														   colDef->compression);
+	else
+		attribute.attcompression = InvalidOid;
+
 	/* attribute.attacl is handled by InsertPgAttributeTuples() */
 
 	ReleaseSysCache(typeTuple);
@@ -11860,6 +11914,22 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 
 	ReleaseSysCache(typeTuple);
 
+	/* Setup attribute compression */
+	if (rel->rd_rel->relkind == RELKIND_RELATION ||
+		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		/*
+		 * InvalidOid for the plain/external storage otherwise default
+		 * compression id.
+		 */
+		if (!IsStorageCompressible(tform->typstorage))
+			attTup->attcompression = InvalidOid;
+		else if (!OidIsValid(attTup->attcompression))
+			attTup->attcompression = DefaultCompressionOid;
+	}
+	else
+		attTup->attcompression = InvalidOid;
+
 	CatalogTupleUpdate(attrelation, &heapTup->t_self, heapTup);
 
 	table_close(attrelation, RowExclusiveLock);
@@ -17665,3 +17735,42 @@ ATExecAlterCollationRefreshVersion(Relation rel, List *coll)
 	index_update_collation_versions(rel->rd_id, get_collation_oid(coll, false));
 	CacheInvalidateRelcache(rel);
 }
+
+/*
+ * resolve column compression specification to an OID.
+ */
+static Oid
+GetAttributeCompression(Form_pg_attribute att, char *compression)
+{
+	char		typstorage = get_typstorage(att->atttypid);
+	Oid			amoid;
+
+	/*
+	 * No compression for the plain/external storage, refer comments atop
+	 * attcompression parameter in pg_attribute.h
+	 */
+	if (!IsStorageCompressible(typstorage))
+	{
+		if (compression == NULL)
+			return InvalidOid;
+
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("column data type %s does not support compression",
+						format_type_be(att->atttypid))));
+	}
+
+	/* fallback to default compression if it's not specified */
+	if (compression == NULL)
+		return DefaultCompressionOid;
+
+	amoid = get_compression_am_oid(compression, false);
+
+#ifndef HAVE_LIBLZ4
+	if (amoid == LZ4_COMPRESSION_AM_OID)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("not built with lz4 support")));
+#endif
+	return amoid;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 2993ba43e3..f77deee399 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -37,6 +37,8 @@
 
 #include "postgres.h"
 
+#include "access/compressamapi.h"
+#include "access/detoast.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/tableam.h"
@@ -2036,6 +2038,113 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
 	return slot;
 }
 
+/*
+ * Compare the compression method of each compressed value with the
+ * compression method of the target attribute.  If the compression method
+ * of the compressed value is not supported in the target attribute then
+ * decompress the value.  If any of the value need to decompress then we
+ * need to store that into the new slot.
+ *
+ * The slot will hold the input slot but if any of the value need to be
+ * decompressed then the new slot will be stored into this and the old
+ * slot will be dropped.
+ */
+TupleTableSlot *
+CompareCompressionMethodAndDecompress(TupleTableSlot *slot,
+									  TupleTableSlot **outslot,
+									  TupleDesc targetTupDesc)
+{
+	int			i;
+	int			attnum;
+	int			natts = slot->tts_tupleDescriptor->natts;
+	bool		decompressed_any = false;
+	bool		slot_tup_deformed = false;
+	Oid			cmoid;
+	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
+
+	if (natts == 0)
+		return slot;
+
+	/*
+	 * Loop over all the attributes in the tuple and check if any attribute is
+	 * compressed and its compression method is not same as the target
+	 * atrribute's compression method then decompress it.
+	 */
+	for (i = 0; i < natts; i++)
+	{
+		attnum = tupleDesc->attrs[i].attnum;
+
+		if (TupleDescAttr(tupleDesc, i)->attlen == -1)
+		{
+			struct varlena *new_value;
+
+			/* fetch the values of all the attribute, if not already done */
+			if (!slot_tup_deformed)
+			{
+				slot_getallattrs(slot);
+				slot_tup_deformed = true;
+			}
+
+			/* nothing to be done, if the value is null */
+			if (slot->tts_isnull[attnum - 1])
+				continue;
+
+			new_value = (struct varlena *)
+				DatumGetPointer(slot->tts_values[attnum - 1]);
+
+			/*
+			 * Get the compression method Oid stored in the toast header and
+			 * compare it with the compression method of the target.
+			 */
+			cmoid = toast_get_compression_oid(new_value);
+			if (OidIsValid(cmoid) &&
+				targetTupDesc->attrs[i].attcompression != cmoid)
+			{
+				new_value = detoast_attr(new_value);
+				slot->tts_values[attnum - 1] = PointerGetDatum(new_value);
+				decompressed_any = true;
+			}
+		}
+	}
+
+	/*
+	 * If we have decompressed any of the fields then we need to copy the
+	 * values/null array from oldslot to the new slot and materialize the new
+	 * slot.
+	 */
+	if (decompressed_any)
+	{
+		TupleTableSlot *newslot = *outslot;
+
+		/*
+		 * If the called has passed an invalid slot then create a new slot.
+		 * Otherwise, just clear the existing tuple from the slot.  This slot
+		 * should be stored by the caller so that it can be reused for
+		 * decompressing the subsequent tuples.
+		 */
+		if (newslot == NULL)
+			newslot = MakeSingleTupleTableSlot(tupleDesc, slot->tts_ops);
+		else
+			ExecClearTuple(newslot);
+
+		/*
+		 * Copy the value/null array to the new slot and materialize it,
+		 * before clearing the tuple from the old slot.
+		 */
+		memcpy(newslot->tts_values, slot->tts_values, natts * sizeof(Datum));
+		memcpy(newslot->tts_isnull, slot->tts_isnull, natts * sizeof(bool));
+		ExecStoreVirtualTuple(newslot);
+		ExecMaterializeSlot(newslot);
+		ExecClearTuple(slot);
+
+		*outslot = newslot;
+
+		return newslot;
+	}
+
+	return slot;
+}
+
 /* ----------------------------------------------------------------
  *	   ExecModifyTable
  *
@@ -2244,6 +2353,15 @@ ExecModifyTable(PlanState *pstate)
 				slot = ExecFilterJunk(junkfilter, slot);
 		}
 
+		/*
+		 * Compare the compression method of the compressed attribute in the
+		 * source tuple with target attribute and if those are different then
+		 * decompress those attributes.
+		 */
+		slot = CompareCompressionMethodAndDecompress(slot,
+									&node->mt_decompress_tuple_slot,
+									resultRelInfo->ri_RelationDesc->rd_att);
+
 		switch (operation)
 		{
 			case CMD_INSERT:
@@ -2876,6 +2994,10 @@ ExecEndModifyTable(ModifyTableState *node)
 			ExecDropSingleTupleTableSlot(node->mt_root_tuple_slot);
 	}
 
+	/* release the slot used for decompressing the tuple */
+	if (node->mt_decompress_tuple_slot)
+		ExecDropSingleTupleTableSlot(node->mt_decompress_tuple_slot);
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 65bbc18ecb..1338e04409 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2966,6 +2966,7 @@ _copyColumnDef(const ColumnDef *from)
 
 	COPY_STRING_FIELD(colname);
 	COPY_NODE_FIELD(typeName);
+	COPY_STRING_FIELD(compression);
 	COPY_SCALAR_FIELD(inhcount);
 	COPY_SCALAR_FIELD(is_local);
 	COPY_SCALAR_FIELD(is_not_null);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index c2d73626fc..f3592003da 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2599,6 +2599,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
 {
 	COMPARE_STRING_FIELD(colname);
 	COMPARE_NODE_FIELD(typeName);
+	COMPARE_STRING_FIELD(compression);
 	COMPARE_SCALAR_FIELD(inhcount);
 	COMPARE_SCALAR_FIELD(is_local);
 	COMPARE_SCALAR_FIELD(is_not_null);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 49357ac5c2..38226530c6 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3897,6 +3897,8 @@ raw_expression_tree_walker(Node *node,
 
 				if (walker(coldef->typeName, context))
 					return true;
+				if (walker(coldef->compression, context))
+					return true;
 				if (walker(coldef->raw_default, context))
 					return true;
 				if (walker(coldef->collClause, context))
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index f5dcedf6e8..0605ef3f84 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2863,6 +2863,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
 
 	WRITE_STRING_FIELD(colname);
 	WRITE_NODE_FIELD(typeName);
+	WRITE_STRING_FIELD(compression);
 	WRITE_INT_FIELD(inhcount);
 	WRITE_BOOL_FIELD(is_local);
 	WRITE_BOOL_FIELD(is_not_null);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index dd72a9fc3c..52d92df25d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -596,6 +596,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <list>		hash_partbound
 %type <defelt>		hash_partbound_elem
 
+%type <str>	optColumnCompression
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -631,9 +633,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CACHE CALL CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
 	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
 	CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS COMMIT
-	COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
-	CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
-	CROSS CSV CUBE CURRENT_P
+	COMMITTED COMPRESSION CONCURRENTLY CONFIGURATION CONFLICT
+	CONNECTION CONSTRAINT CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY
+	COST CREATE CROSS CSV CUBE CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
 
@@ -3421,11 +3423,12 @@ TypedTableElement:
 			| TableConstraint					{ $$ = $1; }
 		;
 
-columnDef:	ColId Typename create_generic_options ColQualList
+columnDef:	ColId Typename optColumnCompression create_generic_options ColQualList
 				{
 					ColumnDef *n = makeNode(ColumnDef);
 					n->colname = $1;
 					n->typeName = $2;
+					n->compression = $3;
 					n->inhcount = 0;
 					n->is_local = true;
 					n->is_not_null = false;
@@ -3434,8 +3437,8 @@ columnDef:	ColId Typename create_generic_options ColQualList
 					n->raw_default = NULL;
 					n->cooked_default = NULL;
 					n->collOid = InvalidOid;
-					n->fdwoptions = $3;
-					SplitColQualList($4, &n->constraints, &n->collClause,
+					n->fdwoptions = $4;
+					SplitColQualList($5, &n->constraints, &n->collClause,
 									 yyscanner);
 					n->location = @1;
 					$$ = (Node *)n;
@@ -3480,6 +3483,14 @@ columnOptions:	ColId ColQualList
 				}
 		;
 
+optColumnCompression:
+					COMPRESSION name
+					{
+						$$ = $2;
+					}
+					| /*EMPTY*/	{ $$ = NULL; }
+				;
+
 ColQualList:
 			ColQualList ColConstraint				{ $$ = lappend($1, $2); }
 			| /*EMPTY*/								{ $$ = NIL; }
@@ -3710,6 +3721,7 @@ TableLikeOption:
 				| INDEXES			{ $$ = CREATE_TABLE_LIKE_INDEXES; }
 				| STATISTICS		{ $$ = CREATE_TABLE_LIKE_STATISTICS; }
 				| STORAGE			{ $$ = CREATE_TABLE_LIKE_STORAGE; }
+				| COMPRESSION		{ $$ = CREATE_TABLE_LIKE_COMPRESSION; }
 				| ALL				{ $$ = CREATE_TABLE_LIKE_ALL; }
 		;
 
@@ -15285,6 +15297,7 @@ unreserved_keyword:
 			| COMMENTS
 			| COMMIT
 			| COMMITTED
+			| COMPRESSION
 			| CONFIGURATION
 			| CONFLICT
 			| CONNECTION
@@ -15805,6 +15818,7 @@ bare_label_keyword:
 			| COMMENTS
 			| COMMIT
 			| COMMITTED
+			| COMPRESSION
 			| CONCURRENTLY
 			| CONFIGURATION
 			| CONFLICT
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index b31f3afa03..cf4413da64 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -27,6 +27,7 @@
 #include "postgres.h"
 
 #include "access/amapi.h"
+#include "access/compressamapi.h"
 #include "access/htup_details.h"
 #include "access/relation.h"
 #include "access/reloptions.h"
@@ -1082,6 +1083,13 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 		else
 			def->storage = 0;
 
+		/* Likewise, copy compression if requested */
+		if ((table_like_clause->options & CREATE_TABLE_LIKE_COMPRESSION) != 0
+			&& OidIsValid(attribute->attcompression))
+			def->compression = get_am_name(attribute->attcompression);
+		else
+			def->compression = NULL;
+
 		/* Likewise, copy comment if requested */
 		if ((table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS) &&
 			(comment = GetComment(attribute->attrelid,
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index c2f910d606..fe133c76c2 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -383,6 +383,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(compression_am_handler);
 PSEUDOTYPE_DUMMY_IO_FUNCS(tsm_handler);
 PSEUDOTYPE_DUMMY_IO_FUNCS(internal);
 PSEUDOTYPE_DUMMY_IO_FUNCS(anyelement);
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 479ed9ae54..a35abe5f1a 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -17,9 +17,11 @@
 #include <ctype.h>
 #include <limits.h>
 
+#include "access/compressamapi.h"
 #include "access/detoast.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_type.h"
+#include "commands/defrem.h"
 #include "common/hashfn.h"
 #include "common/hex.h"
 #include "common/int.h"
@@ -5299,6 +5301,46 @@ pg_column_size(PG_FUNCTION_ARGS)
 	PG_RETURN_INT32(result);
 }
 
+/*
+ * Return the compression method stored in the compressed attribute.  Return
+ * NULL for non varlena type or the uncompressed data.
+ */
+Datum
+pg_column_compression(PG_FUNCTION_ARGS)
+{
+	Datum		value = PG_GETARG_DATUM(0);
+	int			typlen;
+	Oid			cmoid;
+
+	/* On first call, get the input type's typlen, and save at *fn_extra */
+	if (fcinfo->flinfo->fn_extra == NULL)
+	{
+		/* Lookup the datatype of the supplied argument */
+		Oid			argtypeid = get_fn_expr_argtype(fcinfo->flinfo, 0);
+
+		typlen = get_typlen(argtypeid);
+		if (typlen == 0)		/* should not happen */
+			elog(ERROR, "cache lookup failed for type %u", argtypeid);
+
+		fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+													  sizeof(int));
+		*((int *) fcinfo->flinfo->fn_extra) = typlen;
+	}
+	else
+		typlen = *((int *) fcinfo->flinfo->fn_extra);
+
+	if (typlen != -1)
+		PG_RETURN_NULL();
+
+	cmoid =
+		toast_get_compression_oid((struct varlena *) DatumGetPointer(value));
+
+	if (!OidIsValid(cmoid))
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_TEXT_P(cstring_to_text(get_am_name(cmoid)));
+}
+
 /*
  * string_agg - Concatenates values and returns string.
  *
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index eea9f30a79..7332f93a25 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -160,6 +160,7 @@ typedef struct _dumpOptions
 	int			no_subscriptions;
 	int			no_synchronized_snapshots;
 	int			no_unlogged_table_data;
+	int			no_compression_methods;
 	int			serializable_deferrable;
 	int			disable_triggers;
 	int			outputNoTablespaces;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index eb988d7eb4..46044cb92a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -387,6 +387,7 @@ main(int argc, char **argv)
 		{"no-synchronized-snapshots", no_argument, &dopt.no_synchronized_snapshots, 1},
 		{"no-unlogged-table-data", no_argument, &dopt.no_unlogged_table_data, 1},
 		{"no-subscriptions", no_argument, &dopt.no_subscriptions, 1},
+		{"no-compression-methods", no_argument, &dopt.no_compression_methods, 1},
 		{"no-sync", no_argument, NULL, 7},
 		{"on-conflict-do-nothing", no_argument, &dopt.do_nothing, 1},
 		{"rows-per-insert", required_argument, NULL, 10},
@@ -1047,6 +1048,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-compression-methods     do not dump compression methods\n"));
 	printf(_("  --no-synchronized-snapshots  do not use synchronized snapshots in parallel jobs\n"));
 	printf(_("  --no-tablespaces             do not dump tablespace assignments\n"));
 	printf(_("  --no-unlogged-table-data     do not dump unlogged table data\n"));
@@ -8617,6 +8619,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 {
 	DumpOptions *dopt = fout->dopt;
 	PQExpBuffer q = createPQExpBuffer();
+	bool		createWithCompression;
 
 	for (int i = 0; i < numTables; i++)
 	{
@@ -8702,6 +8705,15 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 			appendPQExpBufferStr(q,
 								 "'' AS attidentity,\n");
 
+		createWithCompression = (fout->remoteVersion >= 140000);
+
+		if (createWithCompression)
+			appendPQExpBuffer(q,
+							  "am.amname AS attcmname,\n");
+		else
+			appendPQExpBuffer(q,
+							  "NULL AS attcmname,\n");
+
 		if (fout->remoteVersion >= 110000)
 			appendPQExpBufferStr(q,
 								 "CASE WHEN a.atthasmissing AND NOT a.attisdropped "
@@ -8720,7 +8732,12 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 		/* need left join here to not fail on dropped columns ... */
 		appendPQExpBuffer(q,
 						  "FROM pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_type t "
-						  "ON a.atttypid = t.oid\n"
+						  "ON a.atttypid = t.oid\n");
+
+		if (createWithCompression)
+			appendPQExpBuffer(q, "LEFT JOIN pg_catalog.pg_am am "
+							  "ON a.attcompression = am.oid\n");
+		appendPQExpBuffer(q,
 						  "WHERE a.attrelid = '%u'::pg_catalog.oid "
 						  "AND a.attnum > 0::pg_catalog.int2\n"
 						  "ORDER BY a.attnum",
@@ -8747,6 +8764,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 		tbinfo->attcollation = (Oid *) pg_malloc(ntups * sizeof(Oid));
 		tbinfo->attfdwoptions = (char **) pg_malloc(ntups * sizeof(char *));
 		tbinfo->attmissingval = (char **) pg_malloc(ntups * sizeof(char *));
+		tbinfo->attcmnames = (char **) pg_malloc(ntups * sizeof(char *));
 		tbinfo->notnull = (bool *) pg_malloc(ntups * sizeof(bool));
 		tbinfo->inhNotNull = (bool *) pg_malloc(ntups * sizeof(bool));
 		tbinfo->attrdefs = (AttrDefInfo **) pg_malloc(ntups * sizeof(AttrDefInfo *));
@@ -8775,6 +8793,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 			tbinfo->attcollation[j] = atooid(PQgetvalue(res, j, PQfnumber(res, "attcollation")));
 			tbinfo->attfdwoptions[j] = pg_strdup(PQgetvalue(res, j, PQfnumber(res, "attfdwoptions")));
 			tbinfo->attmissingval[j] = pg_strdup(PQgetvalue(res, j, PQfnumber(res, "attmissingval")));
+			tbinfo->attcmnames[j] = pg_strdup(PQgetvalue(res, j, PQfnumber(res, "attcmname")));
 			tbinfo->attrdefs[j] = NULL; /* fix below */
 			if (PQgetvalue(res, j, PQfnumber(res, "atthasdef"))[0] == 't')
 				hasdefaults = true;
@@ -15832,6 +15851,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 				{
 					bool		print_default;
 					bool		print_notnull;
+					bool		has_non_default_compression;
 
 					/*
 					 * Default value --- suppress if to be printed separately.
@@ -15856,6 +15876,9 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 						!dopt->binary_upgrade)
 						continue;
 
+					has_non_default_compression = (tbinfo->attcmnames[j] &&
+												   (strcmp(tbinfo->attcmnames[j], "pglz") != 0));
+
 					/* Format properly if not first attr */
 					if (actual_atts == 0)
 						appendPQExpBufferStr(q, " (");
@@ -15891,6 +15914,17 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 										  tbinfo->atttypnames[j]);
 					}
 
+					/*
+					 * Attribute compression
+					 */
+					if (!dopt->no_compression_methods &&
+						tbinfo->attcmnames[j] && strlen(tbinfo->attcmnames[j]) &&
+						(has_non_default_compression || dopt->binary_upgrade))
+					{
+						appendPQExpBuffer(q, " COMPRESSION %s",
+										  tbinfo->attcmnames[j]);
+					}
+
 					if (print_default)
 					{
 						if (tbinfo->attgenerated[j] == ATTRIBUTE_GENERATED_STORED)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 0a2213fb06..1789e18f46 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -326,6 +326,7 @@ typedef struct _tableInfo
 	char	   *partbound;		/* partition bound definition */
 	bool		needs_override; /* has GENERATED ALWAYS AS IDENTITY */
 	char	   *amname;			/* relation access method */
+	char	  **attcmnames;		/* per-attribute current compression method */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 737e46464a..97791f8dbe 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2284,9 +2284,9 @@ my %tests = (
 		regexp => qr/^
 			\QCREATE TABLE dump_test.test_table (\E\n
 			\s+\Qcol1 integer NOT NULL,\E\n
-			\s+\Qcol2 text,\E\n
-			\s+\Qcol3 text,\E\n
-			\s+\Qcol4 text,\E\n
+			\s+\Qcol2 text\E\D*,\n
+			\s+\Qcol3 text\E\D*,\n
+			\s+\Qcol4 text\E\D*,\n
 			\s+\QCONSTRAINT test_table_col1_check CHECK ((col1 <= 1000))\E\n
 			\Q)\E\n
 			\QWITH (autovacuum_enabled='false', fillfactor='80');\E\n/xm,
@@ -2326,7 +2326,7 @@ my %tests = (
 		regexp => qr/^
 			\QCREATE TABLE dump_test.test_second_table (\E
 			\n\s+\Qcol1 integer,\E
-			\n\s+\Qcol2 text\E
+			\n\s+\Qcol2 text\E\D*
 			\n\);
 			/xm,
 		like =>
@@ -2441,7 +2441,7 @@ my %tests = (
 			\n\s+\Qcol1 integer,\E
 			\n\s+\Qcol2 boolean,\E
 			\n\s+\Qcol3 boolean,\E
-			\n\s+\Qcol4 bit(5),\E
+			\n\s+\Qcol4 bit(5)\E\D*,
 			\n\s+\Qcol5 double precision\E
 			\n\);
 			/xm,
@@ -2459,7 +2459,7 @@ my %tests = (
 		regexp => qr/^
 			\QCREATE TABLE dump_test.test_table_identity (\E\n
 			\s+\Qcol1 integer NOT NULL,\E\n
-			\s+\Qcol2 text\E\n
+			\s+\Qcol2 text\E\D*\n
 			\);
 			.*
 			\QALTER TABLE dump_test.test_table_identity ALTER COLUMN col1 ADD GENERATED ALWAYS AS IDENTITY (\E\n
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 20af5a92b4..ba464d463e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -170,10 +170,12 @@ describeAccessMethods(const char *pattern, bool verbose)
 					  "  CASE amtype"
 					  " WHEN 'i' THEN '%s'"
 					  " WHEN 't' THEN '%s'"
+					  " WHEN 'c' THEN '%s'"
 					  " END AS \"%s\"",
 					  gettext_noop("Name"),
 					  gettext_noop("Index"),
 					  gettext_noop("Table"),
+					  gettext_noop("Compression"),
 					  gettext_noop("Type"));
 
 	if (verbose)
@@ -1459,7 +1461,7 @@ describeOneTableDetails(const char *schemaname,
 	bool		printTableInitialized = false;
 	int			i;
 	char	   *view_def = NULL;
-	char	   *headers[11];
+	char	   *headers[12];
 	PQExpBufferData title;
 	PQExpBufferData tmpbuf;
 	int			cols;
@@ -1475,7 +1477,8 @@ describeOneTableDetails(const char *schemaname,
 				fdwopts_col = -1,
 				attstorage_col = -1,
 				attstattarget_col = -1,
-				attdescr_col = -1;
+				attdescr_col = -1,
+				attcompression_col = -1;
 	int			numrows;
 	struct
 	{
@@ -1892,6 +1895,20 @@ describeOneTableDetails(const char *schemaname,
 		appendPQExpBufferStr(&buf, ",\n  a.attstorage");
 		attstorage_col = cols++;
 
+		/* compresssion info */
+		if (pset.sversion >= 140000 &&
+			(tableinfo.relkind == RELKIND_RELATION ||
+			 tableinfo.relkind == RELKIND_PARTITIONED_TABLE ||
+			 tableinfo.relkind == RELKIND_MATVIEW))
+		{
+			appendPQExpBufferStr(&buf, ",\n  CASE WHEN attcompression = 0 THEN NULL ELSE "
+								 " (SELECT am.amname "
+								 "  FROM pg_catalog.pg_am am "
+								 "  WHERE am.oid = a.attcompression) "
+								 " END AS attcompression");
+			attcompression_col = cols++;
+		}
+
 		/* stats target, if relevant to relkind */
 		if (tableinfo.relkind == RELKIND_RELATION ||
 			tableinfo.relkind == RELKIND_INDEX ||
@@ -2018,6 +2035,8 @@ describeOneTableDetails(const char *schemaname,
 		headers[cols++] = gettext_noop("FDW options");
 	if (attstorage_col >= 0)
 		headers[cols++] = gettext_noop("Storage");
+	if (attcompression_col >= 0)
+		headers[cols++] = gettext_noop("Compression");
 	if (attstattarget_col >= 0)
 		headers[cols++] = gettext_noop("Stats target");
 	if (attdescr_col >= 0)
@@ -2097,6 +2116,11 @@ describeOneTableDetails(const char *schemaname,
 							  false, false);
 		}
 
+		/* Column compression */
+		if (attcompression_col >= 0)
+			printTableAddCell(&cont, PQgetvalue(res, i, attcompression_col),
+							  false, false);
+
 		/* Statistics target, if the relkind supports this feature */
 		if (attstattarget_col >= 0)
 			printTableAddCell(&cont, PQgetvalue(res, i, attstattarget_col),
diff --git a/src/include/access/compressamapi.h b/src/include/access/compressamapi.h
new file mode 100644
index 0000000000..5a8e23d926
--- /dev/null
+++ b/src/include/access/compressamapi.h
@@ -0,0 +1,99 @@
+/*-------------------------------------------------------------------------
+ *
+ * compressamapi.h
+ *	  API for Postgres compression methods.
+ *
+ * Portions Copyright (c) 2021, PostgreSQL Global Development Group
+ *
+ * src/include/access/compressamapi.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef COMPRESSAMAPI_H
+#define COMPRESSAMAPI_H
+
+#include "postgres.h"
+
+#include "catalog/pg_am_d.h"
+#include "nodes/nodes.h"
+
+/*
+ * Built-in compression method-id.  The toast compression header will store
+ * this in the first 2 bits of the raw length.  These built-in compression
+ * method-id are directly mapped to the built-in compression method oid.
+ */
+typedef enum CompressionId
+{
+	PGLZ_COMPRESSION_ID = 0,
+	LZ4_COMPRESSION_ID = 1
+} CompressionId;
+
+/* Use default compression method if it is not specified. */
+#define DefaultCompressionOid	PGLZ_COMPRESSION_AM_OID
+#define IsStorageCompressible(storage) ((storage) != TYPSTORAGE_PLAIN && \
+										(storage) != TYPSTORAGE_EXTERNAL)
+/* compression handler routines */
+typedef struct varlena *(*cmcompress_function) (const struct varlena *value);
+typedef struct varlena *(*cmdecompress_function) (const struct varlena *value);
+typedef struct varlena *(*cmdecompress_slice_function)
+			(const struct varlena *value, int32 slicelength);
+
+/*
+ * API struct for a compression AM.
+ *
+ * 'datum_compress' - varlena compression function.
+ * 'datum_decompress' - varlena decompression function.
+ * 'datum_decompress_slice' - varlena slice decompression functions.
+ */
+typedef struct CompressionAmRoutine
+{
+	NodeTag		type;
+
+	cmcompress_function datum_compress;
+	cmdecompress_function datum_decompress;
+	cmdecompress_slice_function datum_decompress_slice;
+} CompressionAmRoutine;
+
+extern const CompressionAmRoutine pglz_compress_methods;
+extern const CompressionAmRoutine lz4_compress_methods;
+
+/*
+ * CompressionOidToId - Convert compression Oid to built-in compression id.
+ *
+ * For more details refer comment atop CompressionId in compressamapi.h
+ */
+static inline CompressionId
+CompressionOidToId(Oid cmoid)
+{
+	switch (cmoid)
+	{
+		case PGLZ_COMPRESSION_AM_OID:
+			return PGLZ_COMPRESSION_ID;
+		case LZ4_COMPRESSION_AM_OID:
+			return LZ4_COMPRESSION_ID;
+		default:
+			elog(ERROR, "invalid compression method oid %u", cmoid);
+	}
+}
+
+/*
+ * CompressionIdToOid - Convert built-in compression id to Oid
+ *
+ * For more details refer comment atop CompressionId in compressamapi.h
+ */
+static inline Oid
+CompressionIdToOid(CompressionId cmid)
+{
+	switch (cmid)
+	{
+		case PGLZ_COMPRESSION_ID:
+			return PGLZ_COMPRESSION_AM_OID;
+		case LZ4_COMPRESSION_ID:
+			return LZ4_COMPRESSION_AM_OID;
+		default:
+			elog(ERROR, "invalid compression method id %d", cmid);
+	}
+}
+
+#endif							/* COMPRESSAMAPI_H */
diff --git a/src/include/access/detoast.h b/src/include/access/detoast.h
index 0adf53c77b..6cdc37542d 100644
--- a/src/include/access/detoast.h
+++ b/src/include/access/detoast.h
@@ -89,4 +89,12 @@ extern Size toast_raw_datum_size(Datum value);
  */
 extern Size toast_datum_size(Datum value);
 
+/* ----------
+ * toast_get_compression_oid -
+ *
+ *	Return the compression method oid from the compressed value
+ * ----------
+ */
+extern Oid toast_get_compression_oid(struct varlena *attr);
+
 #endif							/* DETOAST_H */
diff --git a/src/include/access/toast_helper.h b/src/include/access/toast_helper.h
index a9a6d644bc..dca0bc37f3 100644
--- a/src/include/access/toast_helper.h
+++ b/src/include/access/toast_helper.h
@@ -32,6 +32,7 @@ typedef struct
 	struct varlena *tai_oldexternal;
 	int32		tai_size;
 	uint8		tai_colflags;
+	Oid			tai_compression;
 } ToastAttrInfo;
 
 /*
diff --git a/src/include/access/toast_internals.h b/src/include/access/toast_internals.h
index cedfb890d8..31ff91a09c 100644
--- a/src/include/access/toast_internals.h
+++ b/src/include/access/toast_internals.h
@@ -12,6 +12,7 @@
 #ifndef TOAST_INTERNALS_H
 #define TOAST_INTERNALS_H
 
+#include "access/compressamapi.h"
 #include "storage/lockdefs.h"
 #include "utils/relcache.h"
 #include "utils/snapshot.h"
@@ -22,22 +23,22 @@
 typedef struct toast_compress_header
 {
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
-	int32		rawsize;
+	uint32		info;			/* 2 bits for compression method and 30 bits
+								 * rawsize */
 } toast_compress_header;
 
 /*
  * Utilities for manipulation of header information for compressed
  * toast entries.
  */
-#define TOAST_COMPRESS_HDRSZ		((int32) sizeof(toast_compress_header))
-#define TOAST_COMPRESS_RAWSIZE(ptr) (((toast_compress_header *) (ptr))->rawsize)
-#define TOAST_COMPRESS_SIZE(ptr)	((int32) VARSIZE_ANY(ptr) - TOAST_COMPRESS_HDRSZ)
-#define TOAST_COMPRESS_RAWDATA(ptr) \
-	(((char *) (ptr)) + TOAST_COMPRESS_HDRSZ)
-#define TOAST_COMPRESS_SET_RAWSIZE(ptr, len) \
-	(((toast_compress_header *) (ptr))->rawsize = (len))
+#define TOAST_COMPRESS_METHOD(ptr)  (((toast_compress_header *) (ptr))->info >> VARLENA_RAWSIZE_BITS)
+#define TOAST_COMPRESS_SET_SIZE_AND_METHOD(ptr, len, cm_method) \
+	do { \
+		Assert((len) > 0 && (len) <= VARLENA_RAWSIZE_MASK); \
+		((toast_compress_header *) (ptr))->info = ((len) | (cm_method) << VARLENA_RAWSIZE_BITS); \
+	} while (0)
 
-extern Datum toast_compress_datum(Datum value);
+extern Datum toast_compress_datum(Datum value, Oid cmoid);
 extern Oid	toast_get_valid_index(Oid toastoid, LOCKMODE lock);
 
 extern void toast_delete_datum(Relation rel, Datum value, bool is_speculative);
diff --git a/src/include/catalog/pg_am.dat b/src/include/catalog/pg_am.dat
index 6082f0e6a8..605684408c 100644
--- a/src/include/catalog/pg_am.dat
+++ b/src/include/catalog/pg_am.dat
@@ -33,5 +33,11 @@
 { oid => '3580', oid_symbol => 'BRIN_AM_OID',
   descr => 'block range index (BRIN) access method',
   amname => 'brin', amhandler => 'brinhandler', amtype => 'i' },
+{ oid => '4572', oid_symbol => 'PGLZ_COMPRESSION_AM_OID',
+  descr => 'pglz compression access method',
+  amname => 'pglz', amhandler => 'pglzhandler', amtype => 'c' },
+{ oid => '4573', oid_symbol => 'LZ4_COMPRESSION_AM_OID',
+  descr => 'lz4 compression access method',
+  amname => 'lz4', amhandler => 'lz4handler', amtype => 'c' },
 
 ]
diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h
index ced86faef8..65079f3821 100644
--- a/src/include/catalog/pg_am.h
+++ b/src/include/catalog/pg_am.h
@@ -59,6 +59,7 @@ DECLARE_UNIQUE_INDEX_PKEY(pg_am_oid_index, 2652, on pg_am using btree(oid oid_op
  */
 #define AMTYPE_INDEX					'i' /* index access method */
 #define AMTYPE_TABLE					't' /* table access method */
+#define AMTYPE_COMPRESSION				'c'	/* compression access method */
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index 3db42abf08..797e78b17c 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -160,6 +160,12 @@ CATALOG(pg_attribute,1249,AttributeRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(75,
 	/* attribute's collation, if any */
 	Oid			attcollation BKI_LOOKUP_OPT(pg_collation);
 
+	/*
+	 * OID of compression AM.  Must be InvalidOid if and only if typstorage is
+	 * 'plain' or 'external'.
+	 */
+	Oid			attcompression BKI_DEFAULT(0);
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 	/* NOTE: The following fields are not present in tuple descriptors. */
 
@@ -187,7 +193,7 @@ CATALOG(pg_attribute,1249,AttributeRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(75,
  * can access fields beyond attcollation except in a real tuple!
  */
 #define ATTRIBUTE_FIXED_PART_SIZE \
-	(offsetof(FormData_pg_attribute,attcollation) + sizeof(Oid))
+	(offsetof(FormData_pg_attribute,attcompression) + sizeof(Oid))
 
 /* ----------------
  *		Form_pg_attribute corresponds to a pointer to a tuple with
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..4a13844ce6 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -941,6 +941,15 @@
   prorettype => 'void', proargtypes => 'regclass int8',
   prosrc => 'brin_desummarize_range' },
 
+{ oid => '6015', descr => 'pglz compression access method handler',
+  proname => 'pglzhandler', provolatile => 'v',
+  prorettype => 'compression_am_handler', proargtypes => 'internal',
+  prosrc => 'pglzhandler' },
+{ oid => '6016', descr => 'lz4 compression access method handler',
+  proname => 'lz4handler', provolatile => 'v',
+  prorettype => 'compression_am_handler', proargtypes => 'internal',
+  prosrc => 'lz4handler' },
+
 { oid => '338', descr => 'validate an operator class',
   proname => 'amvalidate', provolatile => 'v', prorettype => 'bool',
   proargtypes => 'oid', prosrc => 'amvalidate' },
@@ -7095,6 +7104,10 @@
   descr => 'bytes required to store the value, perhaps with compression',
   proname => 'pg_column_size', provolatile => 's', prorettype => 'int4',
   proargtypes => 'any', prosrc => 'pg_column_size' },
+{ oid => '2121',
+  descr => 'compression method for the compressed datum',
+  proname => 'pg_column_compression', provolatile => 's', prorettype => 'text',
+  proargtypes => 'any', prosrc => 'pg_column_compression' },
 { oid => '2322',
   descr => 'total disk space usage for the specified tablespace',
   proname => 'pg_tablespace_size', provolatile => 'v', prorettype => 'int8',
@@ -7265,6 +7278,13 @@
 { oid => '268', descr => 'I/O',
   proname => 'table_am_handler_out', prorettype => 'cstring',
   proargtypes => 'table_am_handler', prosrc => 'table_am_handler_out' },
+{ oid => '560', descr => 'I/O',
+  proname => 'compression_am_handler_in', proisstrict => 'f',
+  prorettype => 'compression_am_handler', proargtypes => 'cstring',
+  prosrc => 'compression_am_handler_in' },
+{ oid => '561', descr => 'I/O',
+  proname => 'compression_am_handler_out', prorettype => 'cstring',
+  proargtypes => 'compression_am_handler', prosrc => 'compression_am_handler_out' },
 { oid => '5086', descr => 'I/O',
   proname => 'anycompatible_in', prorettype => 'anycompatible',
   proargtypes => 'cstring', prosrc => 'anycompatible_in' },
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index 8959c2f53b..306aabf6c1 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -626,6 +626,11 @@
   typcategory => 'P', typinput => 'index_am_handler_in',
   typoutput => 'index_am_handler_out', typreceive => '-', typsend => '-',
   typalign => 'i' },
+ { oid => '5559',
+  typname => 'compression_am_handler', typlen => '4', typbyval => 't', typtype => 'p',
+  typcategory => 'P', typinput => 'compression_am_handler_in',
+  typoutput => 'compression_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 1a79540c94..e5aea8a240 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -139,6 +139,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_compression_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/executor/executor.h b/src/include/executor/executor.h
index 071e363d54..6495162a33 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -621,5 +621,7 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
 
 extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
 									 const char *relname);
-
+extern TupleTableSlot *CompareCompressionMethodAndDecompress(TupleTableSlot *slot,
+												  TupleTableSlot **outslot,
+												  TupleDesc targetTupDesc);
 #endif							/* EXECUTOR_H  */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index b6a88ff76b..18e8ecfd90 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1182,6 +1182,12 @@ typedef struct ModifyTableState
 	 */
 	TupleTableSlot *mt_root_tuple_slot;
 
+	/*
+	 * Slot for storing the modified tuple, incase the target attribute's
+	 * compression method doesn't match with the source table.
+	 */
+	TupleTableSlot *mt_decompress_tuple_slot;
+
 	/* Tuple-routing support info */
 	struct PartitionTupleRouting *mt_partition_tuple_routing;
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 40ae489c23..20d6f96f62 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -512,6 +512,7 @@ typedef enum NodeTag
 	T_IndexAmRoutine,			/* in access/amapi.h */
 	T_TableAmRoutine,			/* in access/tableam.h */
 	T_TsmRoutine,				/* in access/tsmapi.h */
+	T_CompressionAmRoutine,		/* in access/compressamapi.h */
 	T_ForeignKeyCacheInfo,		/* in utils/rel.h */
 	T_CallContext,				/* in nodes/parsenodes.h */
 	T_SupportRequestSimplify,	/* in nodes/supportnodes.h */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 236832a2ca..19d2ba26bf 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -646,6 +646,7 @@ typedef struct ColumnDef
 	NodeTag		type;
 	char	   *colname;		/* name of column */
 	TypeName   *typeName;		/* type of column */
+	char	   *compression;	/* compression method for column */
 	int			inhcount;		/* number of times column is inherited */
 	bool		is_local;		/* column has local (non-inherited) def'n */
 	bool		is_not_null;	/* NOT NULL constraint specified? */
@@ -685,6 +686,7 @@ typedef enum TableLikeOption
 	CREATE_TABLE_LIKE_INDEXES = 1 << 5,
 	CREATE_TABLE_LIKE_STATISTICS = 1 << 6,
 	CREATE_TABLE_LIKE_STORAGE = 1 << 7,
+	CREATE_TABLE_LIKE_COMPRESSION = 1 << 8,
 	CREATE_TABLE_LIKE_ALL = PG_INT32_MAX
 } TableLikeOption;
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 28083aaac9..ca1f950cbe 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -88,6 +88,7 @@ PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("committed", COMMITTED, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("compression", COMPRESSION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("concurrently", CONCURRENTLY, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("configuration", CONFIGURATION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("conflict", CONFLICT, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 55cab4d2bf..53d378b67d 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -346,6 +346,9 @@
 /* Define to 1 if you have the `z' library (-lz). */
 #undef HAVE_LIBZ
 
+/* Define to 1 if you have the `lz4' library (-llz4). */
+#undef HAVE_LIBLZ4
+
 /* Define to 1 if you have the `link' function. */
 #undef HAVE_LINK
 
diff --git a/src/include/postgres.h b/src/include/postgres.h
index 2ed572004d..667927fd7c 100644
--- a/src/include/postgres.h
+++ b/src/include/postgres.h
@@ -145,7 +145,8 @@ typedef union
 	struct						/* Compressed-in-line format */
 	{
 		uint32		va_header;
-		uint32		va_rawsize; /* Original data size (excludes header) */
+		uint32		va_info;	/* Original data size (excludes header) and
+								 * flags */
 		char		va_data[FLEXIBLE_ARRAY_MEMBER]; /* Compressed data */
 	}			va_compressed;
 } varattrib_4b;
@@ -274,14 +275,21 @@ typedef struct
 	(VARSIZE(PTR) - VARHDRSZ + VARHDRSZ_SHORT)
 
 #define VARHDRSZ_EXTERNAL		offsetof(varattrib_1b_e, va_data)
+#define VARHDRSZ_COMPRESS		offsetof(varattrib_4b, va_compressed.va_data)
 
 #define VARDATA_4B(PTR)		(((varattrib_4b *) (PTR))->va_4byte.va_data)
 #define VARDATA_4B_C(PTR)	(((varattrib_4b *) (PTR))->va_compressed.va_data)
 #define VARDATA_1B(PTR)		(((varattrib_1b *) (PTR))->va_data)
 #define VARDATA_1B_E(PTR)	(((varattrib_1b_e *) (PTR))->va_data)
 
+#define VARLENA_RAWSIZE_BITS	30
+#define VARLENA_RAWSIZE_MASK	((1U << VARLENA_RAWSIZE_BITS) - 1)
+
+/* va_info in va_compress contains raw size of datum and optional flags */
 #define VARRAWSIZE_4B_C(PTR) \
-	(((varattrib_4b *) (PTR))->va_compressed.va_rawsize)
+	(((varattrib_4b *) (PTR))->va_compressed.va_info & VARLENA_RAWSIZE_MASK)
+#define VARFLAGS_4B_C(PTR) \
+	(((varattrib_4b *) (PTR))->va_compressed.va_info >> VARLENA_RAWSIZE_BITS)
 
 /* Externally visible macros */
 
diff --git a/src/test/regress/expected/compression.out b/src/test/regress/expected/compression.out
new file mode 100644
index 0000000000..167878e78b
--- /dev/null
+++ b/src/test/regress/expected/compression.out
@@ -0,0 +1,201 @@
+\set HIDE_COMPRESSAM off
+-- test creating table with compression method
+CREATE TABLE cmdata(f1 text COMPRESSION pglz);
+CREATE INDEX idx ON cmdata(f1);
+INSERT INTO cmdata VALUES(repeat('1234567890',1000));
+\d+ cmdata
+                                 Table "public.cmdata"
+ Column | Type | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+------+-----------+----------+---------+----------+--------------+-------------
+ f1     | text |           |          |         | extended |              | 
+Indexes:
+    "idx" btree (f1)
+
+CREATE TABLE cmdata1(f1 TEXT COMPRESSION lz4);
+INSERT INTO cmdata1 VALUES(repeat('1234567890',1004));
+\d+ cmdata1
+                                 Table "public.cmdata1"
+ Column | Type | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+------+-----------+----------+---------+----------+--------------+-------------
+ f1     | text |           |          |         | extended |              | 
+
+-- try setting compression for incompressible data type
+CREATE TABLE cmdata2 (f1 int COMPRESSION pglz);
+ERROR:  column data type integer does not support compression
+-- verify stored compression method
+SELECT pg_column_compression(f1) FROM cmdata;
+ pg_column_compression 
+-----------------------
+ pglz
+(1 row)
+
+SELECT pg_column_compression(f1) FROM cmdata1;
+ pg_column_compression 
+-----------------------
+ lz4
+(1 row)
+
+-- decompress data slice
+SELECT SUBSTR(f1, 200, 5) FROM cmdata;
+ substr 
+--------
+ 01234
+(1 row)
+
+SELECT SUBSTR(f1, 2000, 50) FROM cmdata1;
+                       substr                       
+----------------------------------------------------
+ 01234567890123456789012345678901234567890123456789
+(1 row)
+
+-- copy with table creation
+SELECT * INTO cmmove1 FROM cmdata;
+SELECT pg_column_compression(f1) FROM cmmove1;
+ pg_column_compression 
+-----------------------
+ pglz
+(1 row)
+
+-- update using datum from different table
+CREATE TABLE cmmove2(f1 text COMPRESSION pglz);
+INSERT INTO cmmove2 VALUES (repeat('1234567890',1004));
+SELECT pg_column_compression(f1) FROM cmmove2;
+ pg_column_compression 
+-----------------------
+ pglz
+(1 row)
+
+UPDATE cmmove2 SET f1 = cmdata.f1 FROM cmdata;
+SELECT pg_column_compression(f1) FROM cmmove2;
+ pg_column_compression 
+-----------------------
+ pglz
+(1 row)
+
+UPDATE cmmove2 SET f1 = cmdata1.f1 FROM cmdata1;
+SELECT pg_column_compression(f1) FROM cmmove2;
+ pg_column_compression 
+-----------------------
+ pglz
+(1 row)
+
+-- copy to existing table
+CREATE TABLE cmmove3(f1 text COMPRESSION pglz);
+INSERT INTO cmmove3 SELECT * FROM cmdata;
+INSERT INTO cmmove3 SELECT * FROM cmdata1;
+SELECT pg_column_compression(f1) FROM cmmove2;
+ pg_column_compression 
+-----------------------
+ pglz
+(1 row)
+
+-- test external compressed data
+CREATE OR REPLACE FUNCTION large_val() RETURNS TEXT LANGUAGE SQL AS
+'select array_agg(md5(g::text))::text from generate_series(1, 256) g';
+CREATE TABLE cmdata2 (f1 text COMPRESSION pglz);
+INSERT INTO cmdata2 select large_val() || repeat('a', 4000);
+SELECT pg_column_compression(f1) FROM cmdata2;
+ pg_column_compression 
+-----------------------
+ pglz
+(1 row)
+
+INSERT INTO cmdata1 SELECT * FROM cmdata2;
+SELECT pg_column_compression(f1) FROM cmdata1;
+ pg_column_compression 
+-----------------------
+ lz4
+ lz4
+(2 rows)
+
+DROP TABLE cmdata2;
+-- test LIKE INCLUDING COMPRESSION
+CREATE TABLE cmdata2 (LIKE cmdata1 INCLUDING COMPRESSION);
+\d+ cmdata2
+                                 Table "public.cmdata2"
+ Column | Type | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+------+-----------+----------+---------+----------+--------------+-------------
+ f1     | text |           |          |         | extended |              | 
+
+-- test compression with materialized view
+CREATE MATERIALIZED VIEW mv(x) AS SELECT * FROM cmdata1;
+\d+ mv
+                             Materialized view "public.mv"
+ Column | Type | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+------+-----------+----------+---------+----------+--------------+-------------
+ x      | text |           |          |         | extended |              | 
+View definition:
+ SELECT cmdata1.f1 AS x
+   FROM cmdata1;
+
+SELECT pg_column_compression(f1) FROM cmdata1;
+ pg_column_compression 
+-----------------------
+ lz4
+ lz4
+(2 rows)
+
+SELECT pg_column_compression(x) FROM mv;
+ pg_column_compression 
+-----------------------
+ pglz
+ pglz
+(2 rows)
+
+-- test compression with partition
+CREATE TABLE cmpart(f1 text COMPRESSION lz4) PARTITION BY HASH(f1);
+CREATE TABLE cmpart1 PARTITION OF cmpart FOR VALUES WITH (MODULUS 2, REMAINDER 0);
+CREATE TABLE cmpart2(f1 text COMPRESSION pglz);
+ALTER TABLE cmpart ATTACH PARTITION cmpart2 FOR VALUES WITH (MODULUS 2, REMAINDER 1);
+INSERT INTO cmpart VALUES (repeat('123456789',1004));
+INSERT INTO cmpart VALUES (repeat('123456789',4004));
+SELECT pg_column_compression(f1) FROM cmpart;
+ pg_column_compression 
+-----------------------
+ lz4
+ pglz
+(2 rows)
+
+-- test compression with inheritence, error
+CREATE TABLE cminh() INHERITS(cmdata, cmdata1);
+NOTICE:  merging multiple inherited definitions of column "f1"
+ERROR:  column "f1" has a compression method conflict
+DETAIL:  pglz versus lz4
+CREATE TABLE cminh(f1 TEXT COMPRESSION lz4) INHERITS(cmdata);
+NOTICE:  merging column "f1" with inherited definition
+ERROR:  column "f1" has a compression method conflict
+DETAIL:  pglz versus lz4
+-- check data is ok
+SELECT length(f1) FROM cmdata;
+ length 
+--------
+  10000
+(1 row)
+
+SELECT length(f1) FROM cmdata1;
+ length 
+--------
+  10040
+  12449
+(2 rows)
+
+SELECT length(f1) FROM cmmove1;
+ length 
+--------
+  10000
+(1 row)
+
+SELECT length(f1) FROM cmmove2;
+ length 
+--------
+  10040
+(1 row)
+
+SELECT length(f1) FROM cmmove3;
+ length 
+--------
+  10000
+  10040
+(2 rows)
+
+\set HIDE_COMPRESSAM on
diff --git a/src/test/regress/expected/compression_1.out b/src/test/regress/expected/compression_1.out
new file mode 100644
index 0000000000..329e7881b0
--- /dev/null
+++ b/src/test/regress/expected/compression_1.out
@@ -0,0 +1,189 @@
+\set HIDE_COMPRESSAM off
+-- test creating table with compression method
+CREATE TABLE cmdata(f1 text COMPRESSION pglz);
+CREATE INDEX idx ON cmdata(f1);
+INSERT INTO cmdata VALUES(repeat('1234567890',1000));
+\d+ cmdata
+                                 Table "public.cmdata"
+ Column | Type | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+------+-----------+----------+---------+----------+--------------+-------------
+ f1     | text |           |          |         | extended |              | 
+Indexes:
+    "idx" btree (f1)
+
+CREATE TABLE cmdata1(f1 TEXT COMPRESSION lz4);
+ERROR:  not built with lz4 support
+INSERT INTO cmdata1 VALUES(repeat('1234567890',1004));
+ERROR:  relation "cmdata1" does not exist
+LINE 1: INSERT INTO cmdata1 VALUES(repeat('1234567890',1004));
+                    ^
+\d+ cmdata1
+-- try setting compression for incompressible data type
+CREATE TABLE cmdata2 (f1 int COMPRESSION pglz);
+ERROR:  column data type integer does not support compression
+-- verify stored compression method
+SELECT pg_column_compression(f1) FROM cmdata;
+ pg_column_compression 
+-----------------------
+ pglz
+(1 row)
+
+SELECT pg_column_compression(f1) FROM cmdata1;
+ERROR:  relation "cmdata1" does not exist
+LINE 1: SELECT pg_column_compression(f1) FROM cmdata1;
+                                              ^
+-- decompress data slice
+SELECT SUBSTR(f1, 200, 5) FROM cmdata;
+ substr 
+--------
+ 01234
+(1 row)
+
+SELECT SUBSTR(f1, 2000, 50) FROM cmdata1;
+ERROR:  relation "cmdata1" does not exist
+LINE 1: SELECT SUBSTR(f1, 2000, 50) FROM cmdata1;
+                                         ^
+-- copy with table creation
+SELECT * INTO cmmove1 FROM cmdata;
+SELECT pg_column_compression(f1) FROM cmmove1;
+ pg_column_compression 
+-----------------------
+ pglz
+(1 row)
+
+-- update using datum from different table
+CREATE TABLE cmmove2(f1 text COMPRESSION pglz);
+INSERT INTO cmmove2 VALUES (repeat('1234567890',1004));
+SELECT pg_column_compression(f1) FROM cmmove2;
+ pg_column_compression 
+-----------------------
+ pglz
+(1 row)
+
+UPDATE cmmove2 SET f1 = cmdata.f1 FROM cmdata;
+SELECT pg_column_compression(f1) FROM cmmove2;
+ pg_column_compression 
+-----------------------
+ pglz
+(1 row)
+
+UPDATE cmmove2 SET f1 = cmdata1.f1 FROM cmdata1;
+ERROR:  relation "cmdata1" does not exist
+LINE 1: UPDATE cmmove2 SET f1 = cmdata1.f1 FROM cmdata1;
+                                                ^
+SELECT pg_column_compression(f1) FROM cmmove2;
+ pg_column_compression 
+-----------------------
+ pglz
+(1 row)
+
+-- copy to existing table
+CREATE TABLE cmmove3(f1 text COMPRESSION pglz);
+INSERT INTO cmmove3 SELECT * FROM cmdata;
+INSERT INTO cmmove3 SELECT * FROM cmdata1;
+ERROR:  relation "cmdata1" does not exist
+LINE 1: INSERT INTO cmmove3 SELECT * FROM cmdata1;
+                                          ^
+SELECT pg_column_compression(f1) FROM cmmove2;
+ pg_column_compression 
+-----------------------
+ pglz
+(1 row)
+
+-- test external compressed data
+CREATE OR REPLACE FUNCTION large_val() RETURNS TEXT LANGUAGE SQL AS
+'select array_agg(md5(g::text))::text from generate_series(1, 256) g';
+CREATE TABLE cmdata2 (f1 text COMPRESSION pglz);
+INSERT INTO cmdata2 select large_val() || repeat('a', 4000);
+SELECT pg_column_compression(f1) FROM cmdata2;
+ pg_column_compression 
+-----------------------
+ pglz
+(1 row)
+
+INSERT INTO cmdata1 SELECT * FROM cmdata2;
+ERROR:  relation "cmdata1" does not exist
+LINE 1: INSERT INTO cmdata1 SELECT * FROM cmdata2;
+                    ^
+SELECT pg_column_compression(f1) FROM cmdata1;
+ERROR:  relation "cmdata1" does not exist
+LINE 1: SELECT pg_column_compression(f1) FROM cmdata1;
+                                              ^
+DROP TABLE cmdata2;
+-- test LIKE INCLUDING COMPRESSION
+CREATE TABLE cmdata2 (LIKE cmdata1 INCLUDING COMPRESSION);
+ERROR:  relation "cmdata1" does not exist
+LINE 1: CREATE TABLE cmdata2 (LIKE cmdata1 INCLUDING COMPRESSION);
+                                   ^
+\d+ cmdata2
+-- test compression with materialized view
+CREATE MATERIALIZED VIEW mv(x) AS SELECT * FROM cmdata1;
+ERROR:  relation "cmdata1" does not exist
+LINE 1: CREATE MATERIALIZED VIEW mv(x) AS SELECT * FROM cmdata1;
+                                                        ^
+\d+ mv
+SELECT pg_column_compression(f1) FROM cmdata1;
+ERROR:  relation "cmdata1" does not exist
+LINE 1: SELECT pg_column_compression(f1) FROM cmdata1;
+                                              ^
+SELECT pg_column_compression(x) FROM mv;
+ERROR:  relation "mv" does not exist
+LINE 1: SELECT pg_column_compression(x) FROM mv;
+                                             ^
+-- test compression with partition
+CREATE TABLE cmpart(f1 text COMPRESSION lz4) PARTITION BY HASH(f1);
+ERROR:  not built with lz4 support
+CREATE TABLE cmpart1 PARTITION OF cmpart FOR VALUES WITH (MODULUS 2, REMAINDER 0);
+ERROR:  relation "cmpart" does not exist
+CREATE TABLE cmpart2(f1 text COMPRESSION pglz);
+ALTER TABLE cmpart ATTACH PARTITION cmpart2 FOR VALUES WITH (MODULUS 2, REMAINDER 1);
+ERROR:  relation "cmpart" does not exist
+INSERT INTO cmpart VALUES (repeat('123456789',1004));
+ERROR:  relation "cmpart" does not exist
+LINE 1: INSERT INTO cmpart VALUES (repeat('123456789',1004));
+                    ^
+INSERT INTO cmpart VALUES (repeat('123456789',4004));
+ERROR:  relation "cmpart" does not exist
+LINE 1: INSERT INTO cmpart VALUES (repeat('123456789',4004));
+                    ^
+SELECT pg_column_compression(f1) FROM cmpart;
+ERROR:  relation "cmpart" does not exist
+LINE 1: SELECT pg_column_compression(f1) FROM cmpart;
+                                              ^
+-- test compression with inheritence, error
+CREATE TABLE cminh() INHERITS(cmdata, cmdata1);
+ERROR:  relation "cmdata1" does not exist
+CREATE TABLE cminh(f1 TEXT COMPRESSION lz4) INHERITS(cmdata);
+NOTICE:  merging column "f1" with inherited definition
+ERROR:  column "f1" has a compression method conflict
+DETAIL:  pglz versus lz4
+-- check data is ok
+SELECT length(f1) FROM cmdata;
+ length 
+--------
+  10000
+(1 row)
+
+SELECT length(f1) FROM cmdata1;
+ERROR:  relation "cmdata1" does not exist
+LINE 1: SELECT length(f1) FROM cmdata1;
+                               ^
+SELECT length(f1) FROM cmmove1;
+ length 
+--------
+  10000
+(1 row)
+
+SELECT length(f1) FROM cmmove2;
+ length 
+--------
+  10000
+(1 row)
+
+SELECT length(f1) FROM cmmove3;
+ length 
+--------
+  10000
+(1 row)
+
+\set HIDE_COMPRESSAM on
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 7204fdb0b4..14f3fa3fc8 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4898,8 +4898,8 @@ Indexes:
 -- check printing info about access methods
 \dA
 List of access methods
-  Name  | Type  
---------+-------
+  Name  |    Type     
+--------+-------------
  brin   | Index
  btree  | Index
  gin    | Index
@@ -4907,13 +4907,15 @@ List of access methods
  hash   | Index
  heap   | Table
  heap2  | Table
+ lz4    | Compression
+ pglz   | Compression
  spgist | Index
-(8 rows)
+(10 rows)
 
 \dA *
 List of access methods
-  Name  | Type  
---------+-------
+  Name  |    Type     
+--------+-------------
  brin   | Index
  btree  | Index
  gin    | Index
@@ -4921,8 +4923,10 @@ List of access methods
  hash   | Index
  heap   | Table
  heap2  | Table
+ lz4    | Compression
+ pglz   | Compression
  spgist | Index
-(8 rows)
+(10 rows)
 
 \dA h*
 List of access methods
@@ -4947,32 +4951,36 @@ 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 | 
+ lz4    | Compression | lz4handler           | lz4 compression access method
+ pglz   | Compression | pglzhandler          | pglz compression access method
+ spgist | Index       | spghandler           | SP-GiST index access method
+(10 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 | 
+ lz4    | Compression | lz4handler           | lz4 compression access method
+ pglz   | Compression | pglzhandler          | pglz compression access method
+ spgist | Index       | spghandler           | SP-GiST index access method
+(10 rows)
 
 \dA+ h*
                      List of access methods
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 12bb67e491..e9c0fc9760 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -114,7 +114,7 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion tr
 # ----------
 # Another group of parallel tests
 # ----------
-test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain
+test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain compression
 
 # event triggers cannot run concurrently with any test that runs DDL
 # oidjoins is read-only, though, and should run late for best coverage
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 59b416fd80..4d5577bae3 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -199,6 +199,7 @@ test: partition_aggregate
 test: partition_info
 test: tuplesort
 test: explain
+test: compression
 test: event_trigger
 test: oidjoins
 test: fast_default
diff --git a/src/test/regress/sql/compression.sql b/src/test/regress/sql/compression.sql
new file mode 100644
index 0000000000..450416ecb4
--- /dev/null
+++ b/src/test/regress/sql/compression.sql
@@ -0,0 +1,85 @@
+\set HIDE_COMPRESSAM off
+
+-- test creating table with compression method
+CREATE TABLE cmdata(f1 text COMPRESSION pglz);
+CREATE INDEX idx ON cmdata(f1);
+INSERT INTO cmdata VALUES(repeat('1234567890',1000));
+\d+ cmdata
+
+CREATE TABLE cmdata1(f1 TEXT COMPRESSION lz4);
+INSERT INTO cmdata1 VALUES(repeat('1234567890',1004));
+\d+ cmdata1
+
+-- try setting compression for incompressible data type
+CREATE TABLE cmdata2 (f1 int COMPRESSION pglz);
+
+-- verify stored compression method
+SELECT pg_column_compression(f1) FROM cmdata;
+SELECT pg_column_compression(f1) FROM cmdata1;
+
+-- decompress data slice
+SELECT SUBSTR(f1, 200, 5) FROM cmdata;
+SELECT SUBSTR(f1, 2000, 50) FROM cmdata1;
+
+-- copy with table creation
+SELECT * INTO cmmove1 FROM cmdata;
+SELECT pg_column_compression(f1) FROM cmmove1;
+
+-- update using datum from different table
+CREATE TABLE cmmove2(f1 text COMPRESSION pglz);
+INSERT INTO cmmove2 VALUES (repeat('1234567890',1004));
+SELECT pg_column_compression(f1) FROM cmmove2;
+
+UPDATE cmmove2 SET f1 = cmdata.f1 FROM cmdata;
+SELECT pg_column_compression(f1) FROM cmmove2;
+UPDATE cmmove2 SET f1 = cmdata1.f1 FROM cmdata1;
+SELECT pg_column_compression(f1) FROM cmmove2;
+
+-- copy to existing table
+CREATE TABLE cmmove3(f1 text COMPRESSION pglz);
+INSERT INTO cmmove3 SELECT * FROM cmdata;
+INSERT INTO cmmove3 SELECT * FROM cmdata1;
+SELECT pg_column_compression(f1) FROM cmmove2;
+
+-- test external compressed data
+CREATE OR REPLACE FUNCTION large_val() RETURNS TEXT LANGUAGE SQL AS
+'select array_agg(md5(g::text))::text from generate_series(1, 256) g';
+CREATE TABLE cmdata2 (f1 text COMPRESSION pglz);
+INSERT INTO cmdata2 select large_val() || repeat('a', 4000);
+SELECT pg_column_compression(f1) FROM cmdata2;
+INSERT INTO cmdata1 SELECT * FROM cmdata2;
+SELECT pg_column_compression(f1) FROM cmdata1;
+DROP TABLE cmdata2;
+
+-- test LIKE INCLUDING COMPRESSION
+CREATE TABLE cmdata2 (LIKE cmdata1 INCLUDING COMPRESSION);
+\d+ cmdata2
+
+-- test compression with materialized view
+CREATE MATERIALIZED VIEW mv(x) AS SELECT * FROM cmdata1;
+\d+ mv
+SELECT pg_column_compression(f1) FROM cmdata1;
+SELECT pg_column_compression(x) FROM mv;
+
+-- test compression with partition
+CREATE TABLE cmpart(f1 text COMPRESSION lz4) PARTITION BY HASH(f1);
+CREATE TABLE cmpart1 PARTITION OF cmpart FOR VALUES WITH (MODULUS 2, REMAINDER 0);
+CREATE TABLE cmpart2(f1 text COMPRESSION pglz);
+
+ALTER TABLE cmpart ATTACH PARTITION cmpart2 FOR VALUES WITH (MODULUS 2, REMAINDER 1);
+INSERT INTO cmpart VALUES (repeat('123456789',1004));
+INSERT INTO cmpart VALUES (repeat('123456789',4004));
+SELECT pg_column_compression(f1) FROM cmpart;
+
+-- test compression with inheritence, error
+CREATE TABLE cminh() INHERITS(cmdata, cmdata1);
+CREATE TABLE cminh(f1 TEXT COMPRESSION lz4) INHERITS(cmdata);
+
+-- check data is ok
+SELECT length(f1) FROM cmdata;
+SELECT length(f1) FROM cmdata1;
+SELECT length(f1) FROM cmmove1;
+SELECT length(f1) FROM cmmove2;
+SELECT length(f1) FROM cmmove3;
+
+\set HIDE_COMPRESSAM on
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 2aa062b2c9..c64b369047 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -307,6 +307,7 @@ sub GenerateFiles
 		HAVE_LIBXML2                                => undef,
 		HAVE_LIBXSLT                                => undef,
 		HAVE_LIBZ                   => $self->{options}->{zlib} ? 1 : undef,
+		HAVE_LIBLZ4                 => undef,
 		HAVE_LINK                   => undef,
 		HAVE_LOCALE_T               => 1,
 		HAVE_LONG_INT_64            => undef,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index bab4f3adb3..46cac11d39 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -395,6 +395,7 @@ CompositeIOData
 CompositeTypeStmt
 CompoundAffixFlag
 CompressionAlgorithm
+CompressionAmRoutine
 CompressorState
 ComputeXidHorizonsResult
 ConditionVariable
-- 
2.17.0


--m51xatjYGsM+13rf
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v24-0002-psql-Add-HIDE_COMPRESSAM-for-regress-testing.patch"



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

* Sequence Access Methods, round two
@ 2023-12-01 05:00 Michael Paquier <[email protected]>
  2023-12-08 06:53 ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]>
  2024-02-08 15:06 ` Re: Sequence Access Methods, round two Peter Eisentraut <[email protected]>
  0 siblings, 2 replies; 8+ messages in thread

From: Michael Paquier @ 2023-12-01 05:00 UTC (permalink / raw)
  To: Postgres hackers <[email protected]>

Hi all,

Back in 2016, a patch set has been proposed to add support for
sequence access methods:
https://www.postgresql.org/message-id/flat/CA%2BU5nMLV3ccdzbqCvcedd-HfrE4dUmoFmTBPL_uJ9YjsQbR7iQ%40m...

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:

  [text/x-diff] v1-0001-Switch-pg_sequence_last_value-to-report-a-tuple-a.patch (5.8K, ../../[email protected]/2-v1-0001-Switch-pg_sequence_last_value-to-report-a-tuple-a.patch)
  download | inline diff:
From 50c77901ee08bf7cf8d9cc80be2df8e78bfcc8bb Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v1-0002-Introduce-sequence_-access-functions.patch (10.4K, ../../[email protected]/3-v1-0002-Introduce-sequence_-access-functions.patch)
  download | inline diff:
From a6574906da8aa0ef7ce2f6df718a3d19a5cd89ed Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v1-0003-Group-more-closely-local-sequence-cache-updates.patch (2.0K, ../../[email protected]/4-v1-0003-Group-more-closely-local-sequence-cache-updates.patch)
  download | inline diff:
From abecdc53a05b0300abfdf0c3d2356dcbe8a3a766 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v1-0004-Remove-FormData_pg_sequence_data-from-init_params.patch (9.3K, ../../[email protected]/5-v1-0004-Remove-FormData_pg_sequence_data-from-init_params.patch)
  download | inline diff:
From 898f7c4940f0d37a2e690fad384502433f02f673 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v1-0005-Integrate-addition-of-attributes-for-sequences-wi.patch (11.1K, ../../[email protected]/6-v1-0005-Integrate-addition-of-attributes-for-sequences-wi.patch)
  download | inline diff:
From 48ba2f63917f2d4c48dbb4bd7085a4ca91830bac Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v1-0006-Sequence-access-methods-backend-support.patch (102.1K, ../../[email protected]/7-v1-0006-Sequence-access-methods-backend-support.patch)
  download | inline diff:
From c7df01143c446193a799f7d2941ff3353f47ab64 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v1-0007-Sequence-access-methods-core-documentation.patch (9.6K, ../../[email protected]/8-v1-0007-Sequence-access-methods-core-documentation.patch)
  download | inline diff:
From b8ed9b7221692110d8ce51b57b751f72e0d102fc Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v1-0008-Sequence-access-methods-dump-restore-support.patch (22.2K, ../../[email protected]/9-v1-0008-Sequence-access-methods-dump-restore-support.patch)
  download | inline diff:
From 10d56dcc2152d92a5f066ddcd856972b3278405e Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v1-0009-dummy_sequence_am-Example-of-sequence-AM.patch (10.1K, ../../[email protected]/10-v1-0009-dummy_sequence_am-Example-of-sequence-AM.patch)
  download | inline diff:
From 81c7a600ed62818e7c81e000d0876e959c6001d6 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [application/pgp-signature] signature.asc (833B, ../../[email protected]/11-signature.asc)
  download

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

* Re: Sequence Access Methods, round two
  2023-12-01 05:00 Sequence Access Methods, round two Michael Paquier <[email protected]>
@ 2023-12-08 06:53 ` Michael Paquier <[email protected]>
  2024-02-22 16:36   ` Re: Sequence Access Methods, round two Tomas Vondra <[email protected]>
  1 sibling, 1 reply; 8+ messages in thread

From: Michael Paquier @ 2023-12-08 06:53 UTC (permalink / raw)
  To: Postgres hackers <[email protected]>

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:

  [text/x-diff] v2-0001-Switch-pg_sequence_last_value-to-report-a-tuple-a.patch (5.8K, ../../[email protected]/2-v2-0001-Switch-pg_sequence_last_value-to-report-a-tuple-a.patch)
  download | inline diff:
From 940aadb33155b90843d6e07fedbe1c13b767c5ea Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v2-0002-Introduce-sequence_-access-functions.patch (10.4K, ../../[email protected]/3-v2-0002-Introduce-sequence_-access-functions.patch)
  download | inline diff:
From b5e7b31998252b2ae98a89e06636f878a6aac4d2 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v2-0003-Group-more-closely-local-sequence-cache-updates.patch (2.0K, ../../[email protected]/4-v2-0003-Group-more-closely-local-sequence-cache-updates.patch)
  download | inline diff:
From ba986c43be4f08c259d1411c6f6d27f399fc1fda Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v2-0004-Remove-FormData_pg_sequence_data-from-init_params.patch (9.3K, ../../[email protected]/5-v2-0004-Remove-FormData_pg_sequence_data-from-init_params.patch)
  download | inline diff:
From f9e8dc91c2defc81d6b69f512b7994d3c9d0c4a4 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v2-0005-Integrate-addition-of-attributes-for-sequences-wi.patch (11.1K, ../../[email protected]/6-v2-0005-Integrate-addition-of-attributes-for-sequences-wi.patch)
  download | inline diff:
From 4ffafa319fd938be33a4fc3632e3db76c89ebeae Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v2-0006-Move-code-for-local-sequences-to-own-file.patch (52.4K, ../../[email protected]/7-v2-0006-Move-code-for-local-sequences-to-own-file.patch)
  download | inline diff:
From 5183081e29f9f5a2e84e25c0fcf28197f12f12cd Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v2-0007-Sequence-access-methods-backend-support.patch (59.5K, ../../[email protected]/8-v2-0007-Sequence-access-methods-backend-support.patch)
  download | inline diff:
From 6b8991043c9d8b7734daf1a0106358850ca77ebc Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v2-0008-Sequence-access-methods-core-documentation.patch (9.6K, ../../[email protected]/9-v2-0008-Sequence-access-methods-core-documentation.patch)
  download | inline diff:
From 83df181f9c8512d887e427e1d8c90ef081db1303 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v2-0009-Sequence-access-methods-dump-restore-support.patch (22.2K, ../../[email protected]/10-v2-0009-Sequence-access-methods-dump-restore-support.patch)
  download | inline diff:
From 1f0bac9ff9defee7207a84cc5982fe52c8ebe0e6 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v2-0010-dummy_sequence_am-Example-of-sequence-AM.patch (10.1K, ../../[email protected]/11-v2-0010-dummy_sequence_am-Example-of-sequence-AM.patch)
  download | inline diff:
From 1bdc9ef40414c049edec154903c40d317387d1c3 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [application/pgp-signature] signature.asc (833B, ../../[email protected]/12-signature.asc)
  download

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

* Re: Sequence Access Methods, round two
  2023-12-01 05:00 Sequence Access Methods, round two Michael Paquier <[email protected]>
  2023-12-08 06:53 ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]>
@ 2024-02-22 16:36   ` Tomas Vondra <[email protected]>
  2024-02-26 08:10     ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]>
  2024-02-27 01:27     ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]>
  0 siblings, 2 replies; 8+ messages in thread

From: Tomas Vondra @ 2024-02-22 16:36 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; Peter Smith <[email protected]>; +Cc: Postgres hackers <[email protected]>

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






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

* Re: Sequence Access Methods, round two
  2023-12-01 05:00 Sequence Access Methods, round two Michael Paquier <[email protected]>
  2023-12-08 06:53 ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]>
  2024-02-22 16:36   ` Re: Sequence Access Methods, round two Tomas Vondra <[email protected]>
@ 2024-02-26 08:10     ` Michael Paquier <[email protected]>
  1 sibling, 0 replies; 8+ messages in thread

From: Michael Paquier @ 2024-02-26 08:10 UTC (permalink / raw)
  To: Tomas Vondra <[email protected]>; +Cc: Peter Smith <[email protected]>; Postgres hackers <[email protected]>

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


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

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

* Re: Sequence Access Methods, round two
  2023-12-01 05:00 Sequence Access Methods, round two Michael Paquier <[email protected]>
  2023-12-08 06:53 ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]>
  2024-02-22 16:36   ` Re: Sequence Access Methods, round two Tomas Vondra <[email protected]>
@ 2024-02-27 01:27     ` Michael Paquier <[email protected]>
  2024-03-11 23:44       ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]>
  1 sibling, 1 reply; 8+ messages in thread

From: Michael Paquier @ 2024-02-27 01:27 UTC (permalink / raw)
  To: Tomas Vondra <[email protected]>; +Cc: Peter Smith <[email protected]>; Postgres hackers <[email protected]>

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:

  [text/x-diff] v3-0001-Switch-pg_sequence_last_value-to-report-a-tuple-a.patch (5.8K, ../../[email protected]/2-v3-0001-Switch-pg_sequence_last_value-to-report-a-tuple-a.patch)
  download | inline diff:
From dd5ac740db59de73723fffb4c1ad22dc1ba64816 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v3-0002-Remove-FormData_pg_sequence_data-from-init_params.patch (9.2K, ../../[email protected]/3-v3-0002-Remove-FormData_pg_sequence_data-from-init_params.patch)
  download | inline diff:
From 3a71c864a0a8a26cf8d88ad940ccee4554df5f7b Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v3-0003-Integrate-addition-of-attributes-for-sequences-wi.patch (11.1K, ../../[email protected]/4-v3-0003-Integrate-addition-of-attributes-for-sequences-wi.patch)
  download | inline diff:
From bdab72d4dd20d160aaae0f04690849a4c697f7ab Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v3-0004-Move-code-for-local-sequences-to-own-file.patch (52.4K, ../../[email protected]/5-v3-0004-Move-code-for-local-sequences-to-own-file.patch)
  download | inline diff:
From fc4cbe04a5b48b0fbd0d9b3effc3435a22ab3b69 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v3-0005-Sequence-access-methods-backend-support.patch (60.7K, ../../[email protected]/6-v3-0005-Sequence-access-methods-backend-support.patch)
  download | inline diff:
From 328dacaf3e5f5b76ca915b076bbfb7c05d135cd4 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v3-0006-Sequence-access-methods-core-documentation.patch (9.6K, ../../[email protected]/7-v3-0006-Sequence-access-methods-core-documentation.patch)
  download | inline diff:
From ccdebdbab1dfbbd8be352888ba668d6bbc7a3653 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v3-0007-Sequence-access-methods-dump-restore-support.patch (22.2K, ../../[email protected]/8-v3-0007-Sequence-access-methods-dump-restore-support.patch)
  download | inline diff:
From bb3149953fbfedb8f900bcc7981c57d3674197e6 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [text/x-diff] v3-0008-dummy_sequence_am-Example-of-sequence-AM.patch (10.1K, ../../[email protected]/9-v3-0008-dummy_sequence_am-Example-of-sequence-AM.patch)
  download | inline diff:
From cfe3e1d5a483bbb2e9cdfee619a8f2da772c886a Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
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



  [application/pgp-signature] signature.asc (833B, ../../[email protected]/10-signature.asc)
  download

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

* Re: Sequence Access Methods, round two
  2023-12-01 05:00 Sequence Access Methods, round two Michael Paquier <[email protected]>
  2023-12-08 06:53 ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]>
  2024-02-22 16:36   ` Re: Sequence Access Methods, round two Tomas Vondra <[email protected]>
  2024-02-27 01:27     ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]>
@ 2024-03-11 23:44       ` Michael Paquier <[email protected]>
  0 siblings, 0 replies; 8+ messages in thread

From: Michael Paquier @ 2024-03-11 23:44 UTC (permalink / raw)
  To: Tomas Vondra <[email protected]>; +Cc: Peter Smith <[email protected]>; Postgres hackers <[email protected]>

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


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

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

* Re: Sequence Access Methods, round two
  2023-12-01 05:00 Sequence Access Methods, round two Michael Paquier <[email protected]>
@ 2024-02-08 15:06 ` Peter Eisentraut <[email protected]>
  1 sibling, 0 replies; 8+ messages in thread

From: Peter Eisentraut @ 2024-02-08 15:06 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; Matthias van de Meent <[email protected]>; +Cc: Postgres hackers <[email protected]>

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.






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


end of thread, other threads:[~2024-03-11 23:44 UTC | newest]

Thread overview: 8+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2021-02-05 12:51 [PATCH v24 01/10] Built-in compression method dilipkumar <[email protected]>
2023-12-01 05:00 Sequence Access Methods, round two Michael Paquier <[email protected]>
2023-12-08 06:53 ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]>
2024-02-22 16:36   ` Re: Sequence Access Methods, round two Tomas Vondra <[email protected]>
2024-02-26 08:10     ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]>
2024-02-27 01:27     ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]>
2024-03-11 23:44       ` Re: Sequence Access Methods, round two Michael Paquier <[email protected]>
2024-02-08 15:06 ` Re: Sequence Access Methods, round two Peter Eisentraut <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox