v1-0006-Sequence-access-methods-backend-support.patch
text/x-diff
Filename: v1-0006-Sequence-access-methods-backend-support.patch
Type: text/x-diff
Part: 5
Message:
Sequence Access Methods, round two
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v1-0006
Subject: Sequence access methods - backend support
| File | + | − |
|---|---|---|
| src/backend/access/rmgrdesc/localseqdesc.c | 9 | 9 |
| src/backend/access/rmgrdesc/Makefile | 1 | 1 |
| src/backend/access/rmgrdesc/meson.build | 1 | 1 |
| src/backend/access/sequence/local.c | 737 | 0 |
| src/backend/access/sequence/Makefile | 1 | 1 |
| src/backend/access/sequence/meson.build | 2 | 0 |
| src/backend/access/sequence/sequenceamapi.c | 145 | 0 |
| src/backend/access/sequence/sequence.c | 2 | 1 |
| src/backend/access/transam/rmgr.c | 1 | 0 |
| src/backend/catalog/heap.c | 2 | 1 |
| src/backend/commands/amcmds.c | 16 | 0 |
| src/backend/commands/sequence.c | 24 | 598 |
| src/backend/commands/tablecmds.c | 10 | 2 |
| src/backend/nodes/gen_node_support.pl | 2 | 0 |
| src/backend/nodes/Makefile | 1 | 0 |
| src/backend/parser/gram.y | 11 | 1 |
| src/backend/parser/parse_utilcmd.c | 2 | 0 |
| src/backend/utils/adt/pseudotypes.c | 1 | 0 |
| src/backend/utils/cache/relcache.c | 68 | 19 |
| src/backend/utils/misc/guc_tables.c | 12 | 0 |
| src/backend/utils/misc/postgresql.conf.sample | 1 | 0 |
| src/bin/pg_waldump/.gitignore | 1 | 1 |
| src/bin/pg_waldump/rmgrdesc.c | 1 | 0 |
| src/bin/pg_waldump/t/001_basic.pl | 1 | 1 |
| src/bin/psql/describe.c | 2 | 0 |
| src/bin/psql/tab-complete.c | 2 | 2 |
| src/include/access/localam.h | 33 | 0 |
| src/include/access/rmgrlist.h | 1 | 1 |
| src/include/access/sequenceam.h | 188 | 0 |
| src/include/catalog/pg_am.dat | 3 | 0 |
| src/include/catalog/pg_am.h | 1 | 0 |
| src/include/catalog/pg_proc.dat | 13 | 0 |
| src/include/catalog/pg_type.dat | 6 | 0 |
| src/include/commands/defrem.h | 1 | 0 |
| src/include/commands/sequence.h | 0 | 34 |
| src/include/nodes/meson.build | 1 | 0 |
| src/include/nodes/parsenodes.h | 1 | 0 |
| src/include/utils/guc_hooks.h | 2 | 0 |
| src/include/utils/rel.h | 5 | 0 |
| src/test/regress/expected/create_am.out | 25 | 8 |
| src/test/regress/expected/opr_sanity.out | 12 | 0 |
| src/test/regress/expected/psql.out | 34 | 30 |
| src/test/regress/sql/create_am.sql | 19 | 5 |
| src/test/regress/sql/opr_sanity.sql | 10 | 0 |
| src/tools/pgindent/typedefs.list | 3 | 2 |
From c7df01143c446193a799f7d2941ff3353f47ab64 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 1 Dec 2023 12:55:02 +0900
Subject: [PATCH v1 6/9] Sequence access methods - backend support
---
src/include/access/localam.h | 33 +
src/include/access/rmgrlist.h | 2 +-
src/include/access/sequenceam.h | 188 +++++
src/include/catalog/pg_am.dat | 3 +
src/include/catalog/pg_am.h | 1 +
src/include/catalog/pg_proc.dat | 13 +
src/include/catalog/pg_type.dat | 6 +
src/include/commands/defrem.h | 1 +
src/include/commands/sequence.h | 34 -
src/include/nodes/meson.build | 1 +
src/include/nodes/parsenodes.h | 1 +
src/include/utils/guc_hooks.h | 2 +
src/include/utils/rel.h | 5 +
src/backend/access/rmgrdesc/Makefile | 2 +-
.../rmgrdesc/{seqdesc.c => localseqdesc.c} | 18 +-
src/backend/access/rmgrdesc/meson.build | 2 +-
src/backend/access/sequence/Makefile | 2 +-
src/backend/access/sequence/local.c | 737 ++++++++++++++++++
src/backend/access/sequence/meson.build | 2 +
src/backend/access/sequence/sequence.c | 3 +-
src/backend/access/sequence/sequenceamapi.c | 145 ++++
src/backend/access/transam/rmgr.c | 1 +
src/backend/catalog/heap.c | 3 +-
src/backend/commands/amcmds.c | 16 +
src/backend/commands/sequence.c | 622 +--------------
src/backend/commands/tablecmds.c | 12 +-
src/backend/nodes/Makefile | 1 +
src/backend/nodes/gen_node_support.pl | 2 +
src/backend/parser/gram.y | 12 +-
src/backend/parser/parse_utilcmd.c | 2 +
src/backend/utils/adt/pseudotypes.c | 1 +
src/backend/utils/cache/relcache.c | 87 ++-
src/backend/utils/misc/guc_tables.c | 12 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/bin/pg_waldump/.gitignore | 2 +-
src/bin/pg_waldump/rmgrdesc.c | 1 +
src/bin/pg_waldump/t/001_basic.pl | 2 +-
src/bin/psql/describe.c | 2 +
src/bin/psql/tab-complete.c | 4 +-
src/test/regress/expected/create_am.out | 33 +-
src/test/regress/expected/opr_sanity.out | 12 +
src/test/regress/expected/psql.out | 64 +-
src/test/regress/sql/create_am.sql | 24 +-
src/test/regress/sql/opr_sanity.sql | 10 +
src/tools/pgindent/typedefs.list | 5 +-
45 files changed, 1414 insertions(+), 718 deletions(-)
create mode 100644 src/include/access/localam.h
create mode 100644 src/include/access/sequenceam.h
rename src/backend/access/rmgrdesc/{seqdesc.c => localseqdesc.c} (69%)
create mode 100644 src/backend/access/sequence/local.c
create mode 100644 src/backend/access/sequence/sequenceamapi.c
diff --git a/src/include/access/localam.h b/src/include/access/localam.h
new file mode 100644
index 0000000000..7afc0a9636
--- /dev/null
+++ b/src/include/access/localam.h
@@ -0,0 +1,33 @@
+/*-------------------------------------------------------------------------
+ *
+ * localam.h
+ * Local sequence access method.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/localam.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef LOCALAM_H
+#define LOCALAM_H
+
+#include "access/xlogreader.h"
+#include "storage/relfilelocator.h"
+
+/* XLOG stuff */
+#define XLOG_LOCAL_SEQ_LOG 0x00
+
+typedef struct xl_local_seq_rec
+{
+ RelFileLocator locator;
+ /* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
+} xl_local_seq_rec;
+
+extern void local_seq_redo(XLogReaderState *record);
+extern void local_seq_desc(StringInfo buf, XLogReaderState *record);
+extern const char *local_seq_identify(uint8 info);
+extern void local_seq_mask(char *page, BlockNumber blkno);
+
+#endif /* LOCALAM_H */
diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h
index 463bcb67c5..544997b01d 100644
--- a/src/include/access/rmgrlist.h
+++ b/src/include/access/rmgrlist.h
@@ -40,7 +40,7 @@ PG_RMGR(RM_BTREE_ID, "Btree", btree_redo, btree_desc, btree_identify, btree_xlog
PG_RMGR(RM_HASH_ID, "Hash", hash_redo, hash_desc, hash_identify, NULL, NULL, hash_mask, NULL)
PG_RMGR(RM_GIN_ID, "Gin", gin_redo, gin_desc, gin_identify, gin_xlog_startup, gin_xlog_cleanup, gin_mask, NULL)
PG_RMGR(RM_GIST_ID, "Gist", gist_redo, gist_desc, gist_identify, gist_xlog_startup, gist_xlog_cleanup, gist_mask, NULL)
-PG_RMGR(RM_SEQ_ID, "Sequence", seq_redo, seq_desc, seq_identify, NULL, NULL, seq_mask, NULL)
+PG_RMGR(RM_LOCAL_SEQ_ID, "LocalSequence", local_seq_redo, local_seq_desc, local_seq_identify, NULL, NULL, local_seq_mask, NULL)
PG_RMGR(RM_SPGIST_ID, "SPGist", spg_redo, spg_desc, spg_identify, spg_xlog_startup, spg_xlog_cleanup, spg_mask, NULL)
PG_RMGR(RM_BRIN_ID, "BRIN", brin_redo, brin_desc, brin_identify, NULL, NULL, brin_mask, NULL)
PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commit_ts_identify, NULL, NULL, NULL, NULL)
diff --git a/src/include/access/sequenceam.h b/src/include/access/sequenceam.h
new file mode 100644
index 0000000000..4052f61269
--- /dev/null
+++ b/src/include/access/sequenceam.h
@@ -0,0 +1,188 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceam.h
+ * POSTGRES sequence access method definitions.
+ *
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/sequenceam.h
+ *
+ * NOTES
+ * See sequenceam.sgml for higher level documentation.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SEQUENCEAM_H
+#define SEQUENCEAM_H
+
+#include "utils/rel.h"
+
+#define DEFAULT_SEQUENCE_ACCESS_METHOD "local"
+
+/* GUCs */
+extern PGDLLIMPORT char *default_sequence_access_method;
+
+/*
+ * API struct for a sequence AM. Note this must be allocated in a
+ * server-lifetime manner, typically as a static const struct, which then gets
+ * returned by FormData_pg_am.amhandler.
+ *
+ * In most cases it's not appropriate to call the callbacks directly, use the
+ * sequence_* wrapper functions instead.
+ *
+ * GetSequenceAmRoutine() asserts that required callbacks are filled in,
+ * remember to update when adding a callback.
+ */
+typedef struct SequenceAmRoutine
+{
+ /* this must be set to T_SequenceAmRoutine */
+ NodeTag type;
+
+ /*
+ * Retrieve table access method used by a sequence to store its metadata.
+ */
+ const char *(*get_table_am) (void);
+
+ /*
+ * Initialize sequence after creating a sequence Relation in pg_class,
+ * setting up the sequence for use. "last_value" and "is_called" are
+ * guessed from the options set for the sequence in CREATE SEQUENCE, based
+ * on the configuration of pg_sequences.
+ */
+ void (*init) (Relation rel, int64 last_value, bool is_called);
+
+ /*
+ * Retrieve a result for nextval(), based on the options retrieved from
+ * the sequence's options in pg_sequences. "last" is the last value
+ * calculated stored in the session's local cache, for lastval().
+ */
+ int64 (*nextval) (Relation rel, int64 incby, int64 maxv,
+ int64 minv, int64 cache, bool cycle,
+ int64 *last);
+
+ /*
+ * Callback to set the state of a sequence, based on the input arguments
+ * from setval().
+ */
+ void (*setval) (Relation rel, int64 next, bool iscalled);
+
+ /*
+ * Reset a sequence to its initial value. "reset_state", if set to true,
+ * means that the sequence parameters have changed, hence its internal
+ * state may need to be reset as well. "startv" and "is_called" are
+ * values guessed from the configuration of the sequence, based on the
+ * contents of pg_sequences.
+ */
+ void (*reset) (Relation rel, int64 startv, bool is_called,
+ bool reset_state);
+
+ /*
+ * Returns the currenr state of a sequence, returning data for
+ * pg_sequence_last_value() and related DDLs like ALTER SEQUENCE.
+ * "last_value" and "is_called" should be assigned to the values retrieved
+ * from the sequence Relation.
+ */
+ void (*get_state) (Relation rel, int64 *last_value, bool *is_called);
+
+ /*
+ * Callback used when switching persistence of a sequence Relation, to
+ * reset the sequence based on its new persistence "newrelpersistence".
+ */
+ void (*change_persistence) (Relation rel, char newrelpersistence);
+
+} SequenceAmRoutine;
+
+
+/* ---------------------------------------------------------------------------
+ * Wrapper functions for each callback.
+ * ---------------------------------------------------------------------------
+ */
+
+/*
+ * Returns the name of the table access method used by this sequence.
+ */
+static inline const char *
+sequence_get_table_am(Relation rel)
+{
+ return rel->rd_sequenceam->get_table_am();
+}
+
+/*
+ * Insert tuple data based on the information guessed from the contents
+ * of pg_sequences.
+ */
+static inline void
+sequence_init(Relation rel, int64 last_value, bool is_called)
+{
+ rel->rd_sequenceam->init(rel, last_value, is_called);
+}
+
+/*
+ * Allocate a set of values for the given sequence. "last" is the last value
+ * allocated. The result returned is the next value of the sequence computed.
+ */
+static inline int64
+sequence_nextval(Relation rel, int64 incby, int64 maxv,
+ int64 minv, int64 cache, bool cycle,
+ int64 *last)
+{
+ return rel->rd_sequenceam->nextval(rel, incby, maxv, minv, cache,
+ cycle, last);
+}
+
+/*
+ * Callback to set the state of a sequence, based on the input arguments
+ * from setval().
+ */
+static inline void
+sequence_setval(Relation rel, int64 next, bool iscalled)
+{
+ rel->rd_sequenceam->setval(rel, next, iscalled);
+}
+
+/*
+ * Reset a sequence to its initial state.
+ */
+static inline void
+sequence_reset(Relation rel, int64 startv, bool is_called,
+ bool reset_state)
+{
+ rel->rd_sequenceam->reset(rel, startv, is_called, reset_state);
+}
+
+/*
+ * Retrieve sequence metadata.
+ */
+static inline void
+sequence_get_state(Relation rel, int64 *last_value, bool *is_called)
+{
+ rel->rd_sequenceam->get_state(rel, last_value, is_called);
+}
+
+/*
+ * Callback to change the persistence of a sequence Relation.
+ */
+static inline void
+sequence_change_persistence(Relation rel, char newrelpersistence)
+{
+ rel->rd_sequenceam->change_persistence(rel, newrelpersistence);
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions in sequenceamapi.c
+ * ----------------------------------------------------------------------------
+ */
+
+extern const SequenceAmRoutine *GetSequenceAmRoutine(Oid amhandler);
+extern Oid GetSequenceAmRoutineId(Oid amoid);
+
+/* ----------------------------------------------------------------------------
+ * Functions in local.c
+ * ----------------------------------------------------------------------------
+ */
+
+extern const SequenceAmRoutine *GetLocalSequenceAmRoutine(void);
+
+#endif /* SEQUENCEAM_H */
diff --git a/src/include/catalog/pg_am.dat b/src/include/catalog/pg_am.dat
index ed641037dd..6de9f0f23d 100644
--- a/src/include/catalog/pg_am.dat
+++ b/src/include/catalog/pg_am.dat
@@ -15,6 +15,9 @@
{ oid => '2', oid_symbol => 'HEAP_TABLE_AM_OID',
descr => 'heap table access method',
amname => 'heap', amhandler => 'heap_tableam_handler', amtype => 't' },
+{ oid => '8047', oid_symbol => 'LOCAL_SEQUENCE_AM_OID',
+ descr => 'local sequence access method',
+ amname => 'local', amhandler => 'local_sequenceam_handler', amtype => 's' },
{ oid => '403', oid_symbol => 'BTREE_AM_OID',
descr => 'b-tree index access method',
amname => 'btree', amhandler => 'bthandler', amtype => 'i' },
diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h
index d5314bb38b..2d10b9c18a 100644
--- a/src/include/catalog/pg_am.h
+++ b/src/include/catalog/pg_am.h
@@ -56,6 +56,7 @@ DECLARE_UNIQUE_INDEX_PKEY(pg_am_oid_index, 2652, AmOidIndexId, pg_am, btree(oid
* Allowed values for amtype
*/
#define AMTYPE_INDEX 'i' /* index access method */
+#define AMTYPE_SEQUENCE 's' /* sequence access method */
#define AMTYPE_TABLE 't' /* table access method */
#endif /* EXPOSE_TO_CLIENT_CODE */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 5999952da3..63bdd117ac 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -913,6 +913,12 @@
prorettype => 'table_am_handler', proargtypes => 'internal',
prosrc => 'heap_tableam_handler' },
+# Sequence access method handlers
+{ oid => '8209', descr => 'local sequence access method handler',
+ proname => 'local_sequenceam_handler', provolatile => 'v',
+ prorettype => 'sequence_am_handler', proargtypes => 'internal',
+ prosrc => 'local_sequenceam_handler' },
+
# Index access method handlers
{ oid => '330', descr => 'btree index access method handler',
proname => 'bthandler', provolatile => 'v', prorettype => 'index_am_handler',
@@ -7598,6 +7604,13 @@
{ oid => '327', descr => 'I/O',
proname => 'index_am_handler_out', prorettype => 'cstring',
proargtypes => 'index_am_handler', prosrc => 'index_am_handler_out' },
+{ oid => '8207', descr => 'I/O',
+ proname => 'sequence_am_handler_in', proisstrict => 'f',
+ prorettype => 'sequence_am_handler', proargtypes => 'cstring',
+ prosrc => 'sequence_am_handler_in' },
+{ oid => '8208', descr => 'I/O',
+ proname => 'sequence_am_handler_out', prorettype => 'cstring',
+ proargtypes => 'sequence_am_handler', prosrc => 'sequence_am_handler_out' },
{ oid => '3311', descr => 'I/O',
proname => 'tsm_handler_in', proisstrict => 'f', prorettype => 'tsm_handler',
proargtypes => 'cstring', prosrc => 'tsm_handler_in' },
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index f6110a850d..46d70c4cc4 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -626,6 +626,12 @@
typcategory => 'P', typinput => 'index_am_handler_in',
typoutput => 'index_am_handler_out', typreceive => '-', typsend => '-',
typalign => 'i' },
+{ oid => '8210',
+ descr => 'pseudo-type for the result of a sequence AM handler function',
+ typname => 'sequence_am_handler', typlen => '4', typbyval => 't',
+ typtype => 'p', typcategory => 'P', typinput => 'sequence_am_handler_in',
+ typoutput => 'sequence_am_handler_out', typreceive => '-', typsend => '-',
+ typalign => 'i' },
{ oid => '3310',
descr => 'pseudo-type for the result of a tablesample method function',
typname => 'tsm_handler', typlen => '4', typbyval => 't', typtype => 'p',
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 179eb9901f..4a6329ee51 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -141,6 +141,7 @@ extern Datum transformGenericOptions(Oid catalogId,
extern ObjectAddress CreateAccessMethod(CreateAmStmt *stmt);
extern Oid get_index_am_oid(const char *amname, bool missing_ok);
extern Oid get_table_am_oid(const char *amname, bool missing_ok);
+extern Oid get_sequence_am_oid(const char *amname, bool missing_ok);
extern Oid get_am_oid(const char *amname, bool missing_ok);
extern char *get_am_name(Oid amOid);
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 7db7b3da7b..1ff652848d 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -22,35 +22,6 @@
#include "storage/relfilelocator.h"
-typedef struct FormData_pg_sequence_data
-{
- int64 last_value;
- int64 log_cnt;
- bool is_called;
-} FormData_pg_sequence_data;
-
-typedef FormData_pg_sequence_data *Form_pg_sequence_data;
-
-/*
- * Columns of a sequence relation
- */
-
-#define SEQ_COL_LASTVAL 1
-#define SEQ_COL_LOG 2
-#define SEQ_COL_CALLED 3
-
-#define SEQ_COL_FIRSTCOL SEQ_COL_LASTVAL
-#define SEQ_COL_LASTCOL SEQ_COL_CALLED
-
-/* XLOG stuff */
-#define XLOG_SEQ_LOG 0x00
-
-typedef struct xl_seq_rec
-{
- RelFileLocator locator;
- /* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
-} xl_seq_rec;
-
extern int64 nextval_internal(Oid relid, bool check_permissions);
extern Datum nextval(PG_FUNCTION_ARGS);
extern List *sequence_options(Oid relid);
@@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid);
extern void ResetSequence(Oid seq_relid);
extern void ResetSequenceCaches(void);
-extern void seq_redo(XLogReaderState *record);
-extern void seq_desc(StringInfo buf, XLogReaderState *record);
-extern const char *seq_identify(uint8 info);
-extern void seq_mask(char *page, BlockNumber blkno);
-
#endif /* SEQUENCE_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index 626dc696d5..4a5ab87c2f 100644
--- a/src/include/nodes/meson.build
+++ b/src/include/nodes/meson.build
@@ -9,6 +9,7 @@ node_support_input_i = [
'nodes/execnodes.h',
'access/amapi.h',
'access/sdir.h',
+ 'access/sequenceam.h',
'access/tableam.h',
'access/tsmapi.h',
'commands/event_trigger.h',
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6947225b64..aab9bfa709 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2972,6 +2972,7 @@ typedef struct CreateSeqStmt
List *options;
Oid ownerId; /* ID of owner, or InvalidOid for default */
bool for_identity;
+ char *accessMethod; /* USING name of access method (eg. local) */
bool if_not_exists; /* just do nothing if it already exists? */
} CreateSeqStmt;
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 3d74483f44..c13b8955a2 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -53,6 +53,8 @@ extern bool check_debug_io_direct(char **newval, void **extra, GucSource source)
extern void assign_debug_io_direct(const char *newval, void *extra);
extern bool check_default_table_access_method(char **newval, void **extra,
GucSource source);
+extern bool check_default_sequence_access_method(char **newval, void **extra,
+ GucSource source);
extern bool check_default_tablespace(char **newval, void **extra,
GucSource source);
extern bool check_default_text_search_config(char **newval, void **extra, GucSource source);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 0ad613c4b8..df226ad6f2 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -187,6 +187,11 @@ typedef struct RelationData
*/
const struct TableAmRoutine *rd_tableam;
+ /*
+ * Sequence access method.
+ */
+ const struct SequenceAmRoutine *rd_sequenceam;
+
/* These are non-NULL only for an index relation: */
Form_pg_index rd_index; /* pg_index tuple describing this index */
/* use "struct" here to avoid needing to include htup.h: */
diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgrdesc/Makefile
index cd95eec37f..dff5a60e68 100644
--- a/src/backend/access/rmgrdesc/Makefile
+++ b/src/backend/access/rmgrdesc/Makefile
@@ -18,13 +18,13 @@ OBJS = \
gistdesc.o \
hashdesc.o \
heapdesc.o \
+ localseqdesc.o \
logicalmsgdesc.o \
mxactdesc.o \
nbtdesc.o \
relmapdesc.o \
replorigindesc.o \
rmgrdesc_utils.o \
- seqdesc.o \
smgrdesc.o \
spgdesc.o \
standbydesc.o \
diff --git a/src/backend/access/rmgrdesc/seqdesc.c b/src/backend/access/rmgrdesc/localseqdesc.c
similarity index 69%
rename from src/backend/access/rmgrdesc/seqdesc.c
rename to src/backend/access/rmgrdesc/localseqdesc.c
index ba60544085..3e8dfda01f 100644
--- a/src/backend/access/rmgrdesc/seqdesc.c
+++ b/src/backend/access/rmgrdesc/localseqdesc.c
@@ -1,7 +1,7 @@
/*-------------------------------------------------------------------------
*
- * seqdesc.c
- * rmgr descriptor routines for commands/sequence.c
+ * localseqdesc.c
+ * rmgr descriptor routines for sequence/local.c
*
* Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@@ -14,31 +14,31 @@
*/
#include "postgres.h"
-#include "commands/sequence.h"
+#include "access/localam.h"
void
-seq_desc(StringInfo buf, XLogReaderState *record)
+local_seq_desc(StringInfo buf, XLogReaderState *record)
{
char *rec = XLogRecGetData(record);
uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
- xl_seq_rec *xlrec = (xl_seq_rec *) rec;
+ xl_local_seq_rec *xlrec = (xl_local_seq_rec *) rec;
- if (info == XLOG_SEQ_LOG)
+ if (info == XLOG_LOCAL_SEQ_LOG)
appendStringInfo(buf, "rel %u/%u/%u",
xlrec->locator.spcOid, xlrec->locator.dbOid,
xlrec->locator.relNumber);
}
const char *
-seq_identify(uint8 info)
+local_seq_identify(uint8 info)
{
const char *id = NULL;
switch (info & ~XLR_INFO_MASK)
{
- case XLOG_SEQ_LOG:
- id = "LOG";
+ case XLOG_LOCAL_SEQ_LOG:
+ id = "LOCAL_SEQ_LOG";
break;
}
diff --git a/src/backend/access/rmgrdesc/meson.build b/src/backend/access/rmgrdesc/meson.build
index f76e87e2d7..46fbf12730 100644
--- a/src/backend/access/rmgrdesc/meson.build
+++ b/src/backend/access/rmgrdesc/meson.build
@@ -11,13 +11,13 @@ rmgr_desc_sources = files(
'gistdesc.c',
'hashdesc.c',
'heapdesc.c',
+ 'localseqdesc.c',
'logicalmsgdesc.c',
'mxactdesc.c',
'nbtdesc.c',
'relmapdesc.c',
'replorigindesc.c',
'rmgrdesc_utils.c',
- 'seqdesc.c',
'smgrdesc.c',
'spgdesc.c',
'standbydesc.c',
diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile
index 9f9d31f542..b89a7e0526 100644
--- a/src/backend/access/sequence/Makefile
+++ b/src/backend/access/sequence/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/access/sequence
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = sequence.o
+OBJS = local.o sequence.o sequenceamapi.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/sequence/local.c b/src/backend/access/sequence/local.c
new file mode 100644
index 0000000000..459a4e4301
--- /dev/null
+++ b/src/backend/access/sequence/local.c
@@ -0,0 +1,737 @@
+/*-------------------------------------------------------------------------
+ *
+ * local.c
+ * Local sequence access manager
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/sequence/local.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/bufmask.h"
+#include "access/htup_details.h"
+#include "access/localam.h"
+#include "access/multixact.h"
+#include "access/reloptions.h"
+#include "access/sequenceam.h"
+#include "access/xact.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/pg_type.h"
+#include "catalog/storage_xlog.h"
+#include "commands/defrem.h"
+#include "commands/tablecmds.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "storage/bufmgr.h"
+#include "utils/builtins.h"
+#include "utils/rel.h"
+#include "utils/typcache.h"
+
+
+/*
+ * We don't want to log each fetching of a value from a sequence,
+ * so we pre-log a few fetches in advance. In the event of
+ * crash we can lose (skip over) as many values as we pre-logged.
+ */
+#define LOCAL_SEQ_LOG_VALS 32
+
+/*
+ * The "special area" of a local sequence's buffer page looks like this.
+ */
+#define LOCAL_SEQ_MAGIC 0x1717
+
+typedef struct local_sequence_magic
+{
+ uint32 magic;
+} local_sequence_magic;
+
+/* Format of tuples stored in heap table associated to local sequences */
+typedef struct FormData_pg_sequence_data
+{
+ int64 last_value;
+ int64 log_cnt;
+ bool is_called;
+} FormData_pg_sequence_data;
+
+typedef FormData_pg_sequence_data *Form_pg_sequence_data;
+
+/*
+ * Columns of a local sequence relation
+ */
+#define SEQ_COL_LASTVAL 1
+#define SEQ_COL_LOG 2
+#define SEQ_COL_CALLED 3
+
+#define SEQ_COL_FIRSTCOL SEQ_COL_LASTVAL
+#define SEQ_COL_LASTCOL SEQ_COL_CALLED
+
+
+/*
+ * We don't want to log each fetching of a value from a sequence,
+ * so we pre-log a few fetches in advance. In the event of
+ * crash we can lose (skip over) as many values as we pre-logged.
+ */
+#define SEQ_LOG_VALS 32
+
+static Form_pg_sequence_data read_seq_tuple(Relation rel,
+ Buffer *buf,
+ HeapTuple seqdatatuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_fork_with_data(Relation rel, HeapTuple tuple,
+ ForkNumber forkNum);
+
+/*
+ * Given an opened sequence relation, lock the page buffer and find the tuple
+ *
+ * *buf receives the reference to the pinned-and-ex-locked buffer
+ * *seqdatatuple receives the reference to the sequence tuple proper
+ * (this arg should point to a local variable of type HeapTupleData)
+ *
+ * Function's return value points to the data payload of the tuple
+ */
+static Form_pg_sequence_data
+read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
+{
+ Page page;
+ ItemId lp;
+ local_sequence_magic *sm;
+ Form_pg_sequence_data seq;
+
+ *buf = ReadBuffer(rel, 0);
+ LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
+
+ page = BufferGetPage(*buf);
+ sm = (local_sequence_magic *) PageGetSpecialPointer(page);
+
+ if (sm->magic != LOCAL_SEQ_MAGIC)
+ elog(ERROR, "bad magic number in sequence \"%s\": %08X",
+ RelationGetRelationName(rel), sm->magic);
+
+ lp = PageGetItemId(page, FirstOffsetNumber);
+ Assert(ItemIdIsNormal(lp));
+
+ /* Note we currently only bother to set these two fields of *seqdatatuple */
+ seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
+ seqdatatuple->t_len = ItemIdGetLength(lp);
+
+ /*
+ * Previous releases of Postgres neglected to prevent SELECT FOR UPDATE on
+ * a sequence, which would leave a non-frozen XID in the sequence tuple's
+ * xmax, which eventually leads to clog access failures or worse. If we
+ * see this has happened, clean up after it. We treat this like a hint
+ * bit update, ie, don't bother to WAL-log it, since we can certainly do
+ * this again if the update gets lost.
+ */
+ Assert(!(seqdatatuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI));
+ if (HeapTupleHeaderGetRawXmax(seqdatatuple->t_data) != InvalidTransactionId)
+ {
+ HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId);
+ seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED;
+ seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID;
+ MarkBufferDirtyHint(*buf, true);
+ }
+
+ seq = (Form_pg_sequence_data) GETSTRUCT(seqdatatuple);
+
+ return seq;
+}
+
+/*
+ * Initialize a sequence's relation with the specified tuple as content
+ *
+ * This handles unlogged sequences by writing to both the main and the init
+ * fork as necessary.
+ */
+static void
+fill_seq_with_data(Relation rel, HeapTuple tuple)
+{
+ fill_seq_fork_with_data(rel, tuple, MAIN_FORKNUM);
+
+ if (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
+ {
+ SMgrRelation srel;
+
+ srel = smgropen(rel->rd_locator, InvalidBackendId);
+ smgrcreate(srel, INIT_FORKNUM, false);
+ log_smgrcreate(&rel->rd_locator, INIT_FORKNUM);
+ fill_seq_fork_with_data(rel, tuple, INIT_FORKNUM);
+ FlushRelationBuffers(rel);
+ smgrclose(srel);
+ }
+}
+
+/*
+ * Initialize a sequence's relation fork with the specified tuple as content
+ */
+static void
+fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum)
+{
+ Buffer buf;
+ Page page;
+ local_sequence_magic *sm;
+ OffsetNumber offnum;
+
+ /* Initialize first page of relation with special magic number */
+
+ buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL,
+ EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK);
+ Assert(BufferGetBlockNumber(buf) == 0);
+
+ page = BufferGetPage(buf);
+
+ PageInit(page, BufferGetPageSize(buf), sizeof(local_sequence_magic));
+ sm = (local_sequence_magic *) PageGetSpecialPointer(page);
+ sm->magic = LOCAL_SEQ_MAGIC;
+
+ /* Now insert sequence tuple */
+
+ /*
+ * Since VACUUM does not process sequences, we have to force the tuple to
+ * have xmin = FrozenTransactionId now. Otherwise it would become
+ * invisible to SELECTs after 2G transactions. It is okay to do this
+ * because if the current transaction aborts, no other xact will ever
+ * examine the sequence tuple anyway.
+ */
+ HeapTupleHeaderSetXmin(tuple->t_data, FrozenTransactionId);
+ HeapTupleHeaderSetXminFrozen(tuple->t_data);
+ HeapTupleHeaderSetCmin(tuple->t_data, FirstCommandId);
+ HeapTupleHeaderSetXmax(tuple->t_data, InvalidTransactionId);
+ tuple->t_data->t_infomask |= HEAP_XMAX_INVALID;
+ ItemPointerSet(&tuple->t_data->t_ctid, 0, FirstOffsetNumber);
+
+ /* check the comment above nextval_internal()'s equivalent call. */
+ if (RelationNeedsWAL(rel))
+ GetTopTransactionId();
+
+ START_CRIT_SECTION();
+
+ MarkBufferDirty(buf);
+
+ offnum = PageAddItem(page, (Item) tuple->t_data, tuple->t_len,
+ InvalidOffsetNumber, false, false);
+ if (offnum != FirstOffsetNumber)
+ elog(ERROR, "failed to add sequence tuple to page");
+
+ /* XLOG stuff */
+ if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM)
+ {
+ xl_local_seq_rec xlrec;
+ XLogRecPtr recptr;
+
+ XLogBeginInsert();
+ XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
+
+ xlrec.locator = rel->rd_locator;
+
+ XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec));
+ XLogRegisterData((char *) tuple->t_data, tuple->t_len);
+
+ recptr = XLogInsert(RM_LOCAL_SEQ_ID, XLOG_LOCAL_SEQ_LOG);
+
+ PageSetLSN(page, recptr);
+ }
+
+ END_CRIT_SECTION();
+
+ UnlockReleaseBuffer(buf);
+}
+
+/*
+ * Mask a Sequence page before performing consistency checks on it.
+ */
+void
+local_seq_mask(char *page, BlockNumber blkno)
+{
+ mask_page_lsn_and_checksum(page);
+
+ mask_unused_space(page);
+}
+
+void
+local_seq_redo(XLogReaderState *record)
+{
+ XLogRecPtr lsn = record->EndRecPtr;
+ uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
+ Buffer buffer;
+ Page page;
+ Page localpage;
+ char *item;
+ Size itemsz;
+ xl_local_seq_rec *xlrec = (xl_local_seq_rec *) XLogRecGetData(record);
+ local_sequence_magic *sm;
+
+ if (info != XLOG_LOCAL_SEQ_LOG)
+ elog(PANIC, "seq_redo: unknown op code %u", info);
+
+ buffer = XLogInitBufferForRedo(record, 0);
+ page = (Page) BufferGetPage(buffer);
+
+ /*
+ * We always reinit the page. However, since this WAL record type is also
+ * used for updating sequences, it's possible that a hot-standby backend
+ * is examining the page concurrently; so we mustn't transiently trash the
+ * buffer. The solution is to build the correct new page contents in
+ * local workspace and then memcpy into the buffer. Then only bytes that
+ * are supposed to change will change, even transiently. We must palloc
+ * the local page for alignment reasons.
+ */
+ localpage = (Page) palloc(BufferGetPageSize(buffer));
+
+ PageInit(localpage, BufferGetPageSize(buffer), sizeof(local_sequence_magic));
+ sm = (local_sequence_magic *) PageGetSpecialPointer(localpage);
+ sm->magic = LOCAL_SEQ_MAGIC;
+
+ item = (char *) xlrec + sizeof(xl_local_seq_rec);
+ itemsz = XLogRecGetDataLen(record) - sizeof(xl_local_seq_rec);
+
+ if (PageAddItem(localpage, (Item) item, itemsz,
+ FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+ elog(PANIC, "local_seq_redo: failed to add item to page");
+
+ PageSetLSN(localpage, lsn);
+
+ memcpy(page, localpage, BufferGetPageSize(buffer));
+ MarkBufferDirty(buffer);
+ UnlockReleaseBuffer(buffer);
+
+ pfree(localpage);
+}
+
+/*
+ * local_nextval()
+ *
+ * Allocate a new value for a local sequence, based on the sequence
+ * configuration.
+ */
+static int64
+local_nextval(Relation rel, int64 incby, int64 maxv,
+ int64 minv, int64 cache, bool cycle,
+ int64 *last)
+{
+ int64 result;
+ int64 fetch;
+ int64 next;
+ int64 rescnt = 0;
+ int64 log;
+ Buffer buf;
+ HeapTupleData seqdatatuple;
+ Form_pg_sequence_data seq;
+ Page page;
+ bool logit = false;
+
+ /* lock page buffer and read tuple */
+ seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+ page = BufferGetPage(buf);
+
+ *last = next = result = seq->last_value;
+ fetch = cache;
+ log = seq->log_cnt;
+
+ if (!seq->is_called)
+ {
+ rescnt++; /* return last_value if not is_called */
+ fetch--;
+ }
+
+ /*
+ * Decide whether we should emit a WAL log record. If so, force up the
+ * fetch count to grab SEQ_LOG_VALS more values than we actually need to
+ * cache. (These will then be usable without logging.)
+ *
+ * If this is the first nextval after a checkpoint, we must force a new
+ * WAL record to be written anyway, else replay starting from the
+ * checkpoint would fail to advance the sequence past the logged values.
+ * In this case we may as well fetch extra values.
+ */
+ if (log < fetch || !seq->is_called)
+ {
+ /* forced log to satisfy local demand for values */
+ fetch = log = fetch + SEQ_LOG_VALS;
+ logit = true;
+ }
+ else
+ {
+ XLogRecPtr redoptr = GetRedoRecPtr();
+
+ if (PageGetLSN(page) <= redoptr)
+ {
+ /* last update of seq was before checkpoint */
+ fetch = log = fetch + SEQ_LOG_VALS;
+ logit = true;
+ }
+ }
+
+ while (fetch) /* try to fetch cache [+ log ] numbers */
+ {
+ /*
+ * Check MAXVALUE for ascending sequences and MINVALUE for descending
+ * sequences
+ */
+ if (incby > 0)
+ {
+ /* ascending sequence */
+ if ((maxv >= 0 && next > maxv - incby) ||
+ (maxv < 0 && next + incby > maxv))
+ {
+ if (rescnt > 0)
+ break; /* stop fetching */
+ if (!cycle)
+ ereport(ERROR,
+ (errcode(ERRCODE_SEQUENCE_GENERATOR_LIMIT_EXCEEDED),
+ errmsg("nextval: reached maximum value of sequence \"%s\" (%lld)",
+ RelationGetRelationName(rel),
+ (long long) maxv)));
+ next = minv;
+ }
+ else
+ next += incby;
+ }
+ else
+ {
+ /* descending sequence */
+ if ((minv < 0 && next < minv - incby) ||
+ (minv >= 0 && next + incby < minv))
+ {
+ if (rescnt > 0)
+ break; /* stop fetching */
+ if (!cycle)
+ ereport(ERROR,
+ (errcode(ERRCODE_SEQUENCE_GENERATOR_LIMIT_EXCEEDED),
+ errmsg("nextval: reached minimum value of sequence \"%s\" (%lld)",
+ RelationGetRelationName(rel),
+ (long long) minv)));
+ next = maxv;
+ }
+ else
+ next += incby;
+ }
+ fetch--;
+ if (rescnt < cache)
+ {
+ log--;
+ rescnt++;
+ *last = next;
+ if (rescnt == 1) /* if it's first result - */
+ result = next; /* it's what to return */
+ }
+ }
+
+ log -= fetch; /* adjust for any unfetched numbers */
+ Assert(log >= 0);
+
+ /*
+ * If something needs to be WAL logged, acquire an xid, so this
+ * transaction's commit will trigger a WAL flush and wait for syncrep.
+ * It's sufficient to ensure the toplevel transaction has an xid, no need
+ * to assign xids subxacts, that'll already trigger an appropriate wait.
+ * (Have to do that here, so we're outside the critical section)
+ */
+ if (logit && RelationNeedsWAL(rel))
+ GetTopTransactionId();
+
+ /* ready to change the on-disk (or really, in-buffer) tuple */
+ START_CRIT_SECTION();
+
+ /*
+ * We must mark the buffer dirty before doing XLogInsert(); see notes in
+ * SyncOneBuffer(). However, we don't apply the desired changes just yet.
+ * This looks like a violation of the buffer update protocol, but it is in
+ * fact safe because we hold exclusive lock on the buffer. Any other
+ * process, including a checkpoint, that tries to examine the buffer
+ * contents will block until we release the lock, and then will see the
+ * final state that we install below.
+ */
+ MarkBufferDirty(buf);
+
+ /* XLOG stuff */
+ if (logit && RelationNeedsWAL(rel))
+ {
+ xl_local_seq_rec xlrec;
+ XLogRecPtr recptr;
+
+ /*
+ * We don't log the current state of the tuple, but rather the state
+ * as it would appear after "log" more fetches. This lets us skip
+ * that many future WAL records, at the cost that we lose those
+ * sequence values if we crash.
+ */
+ XLogBeginInsert();
+ XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
+
+ /* set values that will be saved in xlog */
+ seq->last_value = next;
+ seq->is_called = true;
+ seq->log_cnt = 0;
+
+ xlrec.locator = rel->rd_locator;
+
+ XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec));
+ XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len);
+
+ recptr = XLogInsert(RM_LOCAL_SEQ_ID, XLOG_LOCAL_SEQ_LOG);
+
+ PageSetLSN(page, recptr);
+ }
+
+ /* Now update sequence tuple to the intended final state */
+ seq->last_value = *last; /* last fetched number */
+ seq->is_called = true;
+ seq->log_cnt = log; /* how much is logged */
+
+ END_CRIT_SECTION();
+
+ UnlockReleaseBuffer(buf);
+
+ return result;
+}
+
+/*
+ * local_get_table_am()
+ *
+ * Return the table access method used by this sequence.
+ */
+static const char *
+local_get_table_am(void)
+{
+ return "heap";
+}
+
+/*
+ * local_init()
+ *
+ * Add the sequence attributes to the relation created for this sequence
+ * AM and insert a tuple of metadata into the sequence relation, based on
+ * the information guessed from pg_sequences. This is the first tuple
+ * inserted after the relation has been created, filling in its heap
+ * table.
+ */
+static void
+local_init(Relation rel, int64 last_value, bool is_called)
+{
+ Datum value[SEQ_COL_LASTCOL];
+ bool null[SEQ_COL_LASTCOL];
+ List *elts = NIL;
+ List *atcmds = NIL;
+ ListCell *lc;
+ TupleDesc tupdesc;
+ HeapTuple tuple;
+
+ /*
+ * Create relation (and fill value[] and null[] for the initial tuple).
+ */
+ for (int i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++)
+ {
+ ColumnDef *coldef = NULL;
+
+ switch (i)
+ {
+ case SEQ_COL_LASTVAL:
+ coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
+ value[i - 1] = Int64GetDatumFast(last_value);
+ break;
+ case SEQ_COL_LOG:
+ coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
+ value[i - 1] = Int64GetDatum(0);
+ break;
+ case SEQ_COL_CALLED:
+ coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid);
+ value[i - 1] = BoolGetDatum(is_called);
+ break;
+ }
+
+ coldef->is_not_null = true;
+ null[i - 1] = false;
+ elts = lappend(elts, coldef);
+ }
+
+ /* Add all the attributes to the sequence */
+ foreach(lc, elts)
+ {
+ AlterTableCmd *atcmd;
+
+ atcmd = makeNode(AlterTableCmd);
+ atcmd->subtype = AT_AddColumnToSequence;
+ atcmd->def = (Node *) lfirst(lc);
+ atcmds = lappend(atcmds, atcmd);
+ }
+
+ /*
+ * No recursion needed. Note that EventTriggerAlterTableStart() should
+ * have been called.
+ */
+ AlterTableInternal(RelationGetRelid(rel), atcmds, false);
+ CommandCounterIncrement();
+
+ tupdesc = RelationGetDescr(rel);
+ tuple = heap_form_tuple(tupdesc, value, null);
+ fill_seq_with_data(rel, tuple);
+}
+
+/*
+ * local_setval()
+ *
+ * Callback for setval().
+ */
+static void
+local_setval(Relation rel, int64 next, bool iscalled)
+{
+ Buffer buf;
+ HeapTupleData seqdatatuple;
+ Form_pg_sequence_data seq;
+
+ /* lock page buffer and read tuple */
+ seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+ /* ready to change the on-disk (or really, in-buffer) tuple */
+ START_CRIT_SECTION();
+ seq->last_value = next; /* last fetched number */
+ seq->is_called = iscalled;
+ seq->log_cnt = 0;
+
+ MarkBufferDirty(buf);
+
+ /* XLOG stuff */
+ if (RelationNeedsWAL(rel))
+ {
+ xl_local_seq_rec xlrec;
+ XLogRecPtr recptr;
+ Page page = BufferGetPage(buf);
+
+ XLogBeginInsert();
+ XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
+
+ xlrec.locator = rel->rd_locator;
+ XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec));
+ XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len);
+
+ recptr = XLogInsert(RM_LOCAL_SEQ_ID, XLOG_LOCAL_SEQ_LOG);
+
+ PageSetLSN(page, recptr);
+ }
+
+ END_CRIT_SECTION();
+
+ UnlockReleaseBuffer(buf);
+}
+
+/*
+ * local_reset()
+ *
+ * Perform a hard reset on the local sequence, rewriting its heap data
+ * entirely.
+ */
+static void
+local_reset(Relation rel, int64 startv, bool is_called, bool reset_state)
+{
+ Form_pg_sequence_data seq;
+ Buffer buf;
+ HeapTupleData seqdatatuple;
+ HeapTuple tuple;
+
+ /* lock buffer page and read tuple */
+ (void) read_seq_tuple(rel, &buf, &seqdatatuple);
+
+ /*
+ * Copy the existing sequence tuple.
+ */
+ tuple = heap_copytuple(&seqdatatuple);
+
+ /* Now we're done with the old page */
+ UnlockReleaseBuffer(buf);
+
+ /*
+ * Modify the copied tuple to execute the restart (compare the RESTART
+ * action in AlterSequence)
+ */
+ seq = (Form_pg_sequence_data) GETSTRUCT(tuple);
+ seq->last_value = startv;
+ seq->is_called = is_called;
+ if (reset_state)
+ seq->log_cnt = 0;
+
+ /*
+ * Create a new storage file for the sequence.
+ */
+ RelationSetNewRelfilenumber(rel, rel->rd_rel->relpersistence);
+
+ /*
+ * Ensure sequence's relfrozenxid is at 0, since it won't contain any
+ * unfrozen XIDs. Same with relminmxid, since a sequence will never
+ * contain multixacts.
+ */
+ Assert(rel->rd_rel->relfrozenxid == InvalidTransactionId);
+ Assert(rel->rd_rel->relminmxid == InvalidMultiXactId);
+
+ /*
+ * Insert the modified tuple into the new storage file.
+ */
+ fill_seq_with_data(rel, tuple);
+}
+
+/*
+ * local_get_state()
+ *
+ * Retrieve the state of a local sequence.
+ */
+static void
+local_get_state(Relation rel, int64 *last_value, bool *is_called)
+{
+ Buffer buf;
+ HeapTupleData seqdatatuple;
+ Form_pg_sequence_data seq;
+
+ /* lock page buffer and read tuple */
+ seq = read_seq_tuple(rel, &buf, &seqdatatuple);
+
+ *last_value = seq->last_value;
+ *is_called = seq->is_called;
+
+ UnlockReleaseBuffer(buf);
+}
+
+/*
+ * local_change_persistence()
+ *
+ * Persistence change for the local sequence Relation.
+ */
+static void
+local_change_persistence(Relation rel, char newrelpersistence)
+{
+ Buffer buf;
+ HeapTupleData seqdatatuple;
+
+ (void) read_seq_tuple(rel, &buf, &seqdatatuple);
+ RelationSetNewRelfilenumber(rel, newrelpersistence);
+ fill_seq_with_data(rel, &seqdatatuple);
+ UnlockReleaseBuffer(buf);
+}
+
+/* ------------------------------------------------------------------------
+ * Definition of the local sequence access method.
+ * ------------------------------------------------------------------------
+ */
+static const SequenceAmRoutine local_methods = {
+ .type = T_SequenceAmRoutine,
+ .get_table_am = local_get_table_am,
+ .init = local_init,
+ .nextval = local_nextval,
+ .setval = local_setval,
+ .reset = local_reset,
+ .get_state = local_get_state,
+ .change_persistence = local_change_persistence
+};
+
+Datum
+local_sequenceam_handler(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_POINTER(&local_methods);
+}
diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/sequence/meson.build
index 1840a913bc..feae8e5884 100644
--- a/src/backend/access/sequence/meson.build
+++ b/src/backend/access/sequence/meson.build
@@ -1,5 +1,7 @@
# Copyright (c) 2022-2023, PostgreSQL Global Development Group
backend_sources += files(
+ 'local.c',
'sequence.c',
+ 'sequenceamapi.c',
)
diff --git a/src/backend/access/sequence/sequence.c b/src/backend/access/sequence/sequence.c
index a5b9fccb50..64e023f0b4 100644
--- a/src/backend/access/sequence/sequence.c
+++ b/src/backend/access/sequence/sequence.c
@@ -13,7 +13,8 @@
*
* NOTES
* This file contains sequence_ routines that implement access to sequences
- * (in contrast to other relation types like indexes).
+ * (in contrast to other relation types like indexes) that are independent
+ * of individual sequence access methods.
*
*-------------------------------------------------------------------------
*/
diff --git a/src/backend/access/sequence/sequenceamapi.c b/src/backend/access/sequence/sequenceamapi.c
new file mode 100644
index 0000000000..4314b9ecb5
--- /dev/null
+++ b/src/backend/access/sequence/sequenceamapi.c
@@ -0,0 +1,145 @@
+/*-------------------------------------------------------------------------
+ *
+ * sequenceamapi.c
+ * general sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/sequence/sequenceamapi.c
+ *
+ *
+ * Sequence access method allows the SQL Standard Sequence objects to be
+ * managed according to either the default access method or a pluggable
+ * replacement. Each sequence can only use one access method at a time,
+ * though different sequence access methods can be in use by different
+ * sequences at the same time.
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "access/sequenceam.h"
+#include "catalog/pg_am.h"
+#include "commands/defrem.h"
+#include "miscadmin.h"
+#include "utils/guc_hooks.h"
+#include "utils/syscache.h"
+
+
+/* GUC */
+char *default_sequence_access_method = DEFAULT_SEQUENCE_ACCESS_METHOD;
+
+/*
+ * GetSequenceAmRoutine
+ * Call the specified access method handler routine to get its
+ * SequenceAmRoutine struct, which will be palloc'd in the caller's
+ * memory context.
+ */
+const SequenceAmRoutine *
+GetSequenceAmRoutine(Oid amhandler)
+{
+ Datum datum;
+ SequenceAmRoutine *routine;
+
+ datum = OidFunctionCall0(amhandler);
+ routine = (SequenceAmRoutine *) DatumGetPointer(datum);
+
+ if (routine == NULL || !IsA(routine, SequenceAmRoutine))
+ elog(ERROR, "sequence access method handler %u did not return a SequenceAmRoutine struct",
+ amhandler);
+
+ /*
+ * Assert that all required callbacks are present. That makes it a bit
+ * easier to keep AMs up to date, e.g. when forward porting them to a new
+ * major version.
+ */
+ Assert(routine->get_table_am != NULL);
+ Assert(routine->init != NULL);
+ Assert(routine->nextval != NULL);
+ Assert(routine->setval != NULL);
+ Assert(routine->reset != NULL);
+ Assert(routine->get_state != NULL);
+ Assert(routine->change_persistence != NULL);
+
+ return routine;
+}
+
+/*
+ * GetSequenceAmRoutineId
+ * Call pg_am and retrieve the OID of the access method handler.
+ */
+Oid
+GetSequenceAmRoutineId(Oid amoid)
+{
+ Oid amhandleroid;
+ HeapTuple tuple;
+ Form_pg_am aform;
+
+ tuple = SearchSysCache1(AMOID,
+ ObjectIdGetDatum(amoid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for access method %u", amoid);
+ aform = (Form_pg_am) GETSTRUCT(tuple);
+ Assert(aform->amtype == AMTYPE_SEQUENCE);
+ amhandleroid = aform->amhandler;
+ ReleaseSysCache(tuple);
+
+ return amhandleroid;
+}
+
+/* check_hook: validate new default_sequence_access_method */
+bool
+check_default_sequence_access_method(char **newval, void **extra,
+ GucSource source)
+{
+ if (**newval == '\0')
+ {
+ GUC_check_errdetail("%s cannot be empty.",
+ "default_sequence_access_method");
+ return false;
+ }
+
+ if (strlen(*newval) >= NAMEDATALEN)
+ {
+ GUC_check_errdetail("%s is too long (maximum %d characters).",
+ "default_sequence_access_method", NAMEDATALEN - 1);
+ return false;
+ }
+
+ /*
+ * If we aren't inside a transaction, or not connected to a database, we
+ * cannot do the catalog access necessary to verify the method. Must
+ * accept the value on faith.
+ */
+ if (IsTransactionState() && MyDatabaseId != InvalidOid)
+ {
+ if (!OidIsValid(get_sequence_am_oid(*newval, true)))
+ {
+ /*
+ * When source == PGC_S_TEST, don't throw a hard error for a
+ * nonexistent sequence access method, only a NOTICE. See comments
+ * in guc.h.
+ */
+ if (source == PGC_S_TEST)
+ {
+ ereport(NOTICE,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("sequence access method \"%s\" does not exist",
+ *newval)));
+ }
+ else
+ {
+ GUC_check_errdetail("sequence access method \"%s\" does not exist.",
+ *newval);
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index 7d67eda5f7..c3f9acb064 100644
--- a/src/backend/access/transam/rmgr.c
+++ b/src/backend/access/transam/rmgr.c
@@ -15,6 +15,7 @@
#include "access/gistxlog.h"
#include "access/hash_xlog.h"
#include "access/heapam_xlog.h"
+#include "access/localam.h"
#include "access/multixact.h"
#include "access/nbtxlog.h"
#include "access/spgxlog.h"
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 7224d96695..6aa0a3f9e7 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1452,7 +1452,8 @@ heap_create_with_catalog(const char *relname,
* No need to add an explicit dependency for the toast table, as the
* main table depends on it.
*/
- if (RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE)
+ if ((RELKIND_HAS_TABLE_AM(relkind) && relkind != RELKIND_TOASTVALUE) ||
+ relkind == RELKIND_SEQUENCE)
{
ObjectAddressSet(referenced, AccessMethodRelationId, accessmtd);
add_exact_object_address(&referenced, addrs);
diff --git a/src/backend/commands/amcmds.c b/src/backend/commands/amcmds.c
index 2050619123..688b2163d3 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -15,6 +15,7 @@
#include "access/htup_details.h"
#include "access/table.h"
+#include "access/sequenceam.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
@@ -175,6 +176,16 @@ get_table_am_oid(const char *amname, bool missing_ok)
return get_am_type_oid(amname, AMTYPE_TABLE, missing_ok);
}
+/*
+ * get_sequence_am_oid - given an access method name, look up its OID
+ * and verify it corresponds to an sequence AM.
+ */
+Oid
+get_sequence_am_oid(const char *amname, bool missing_ok)
+{
+ return get_am_type_oid(amname, AMTYPE_SEQUENCE, missing_ok);
+}
+
/*
* get_am_oid - given an access method name, look up its OID.
* The type is not checked.
@@ -215,6 +226,8 @@ get_am_type_string(char amtype)
{
case AMTYPE_INDEX:
return "INDEX";
+ case AMTYPE_SEQUENCE:
+ return "SEQUENCE";
case AMTYPE_TABLE:
return "TABLE";
default:
@@ -251,6 +264,9 @@ lookup_am_handler_func(List *handler_name, char amtype)
case AMTYPE_INDEX:
expectedType = INDEX_AM_HANDLEROID;
break;
+ case AMTYPE_SEQUENCE:
+ expectedType = SEQUENCE_AM_HANDLEROID;
+ break;
case AMTYPE_TABLE:
expectedType = TABLE_AM_HANDLEROID;
break;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index bf6e867560..24a515441a 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -19,6 +19,7 @@
#include "access/multixact.h"
#include "access/relation.h"
#include "access/sequence.h"
+#include "access/sequenceam.h"
#include "access/table.h"
#include "access/transam.h"
#include "access/xact.h"
@@ -50,23 +51,6 @@
#include "utils/varlena.h"
-/*
- * We don't want to log each fetching of a value from a sequence,
- * so we pre-log a few fetches in advance. In the event of
- * crash we can lose (skip over) as many values as we pre-logged.
- */
-#define SEQ_LOG_VALS 32
-
-/*
- * The "special area" of a sequence's buffer page looks like this.
- */
-#define SEQ_MAGIC 0x1717
-
-typedef struct sequence_magic
-{
- uint32 magic;
-} sequence_magic;
-
/*
* We store a SeqTable item for every sequence we have touched in the current
* session. This is needed to hold onto nextval/currval state. (We can't
@@ -96,13 +80,9 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
*/
static SeqTableData *last_used_seq = NULL;
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
-static void fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum);
static Relation lock_and_open_sequence(SeqTable seq);
static void create_seq_hashtable(void);
static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
-static Form_pg_sequence_data read_seq_tuple(Relation rel,
- Buffer *buf, HeapTuple seqdatatuple);
static void init_params(ParseState *pstate, List *options, bool for_identity,
bool isInit,
Form_pg_sequence seqform,
@@ -134,14 +114,8 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
Relation rel;
HeapTuple tuple;
TupleDesc tupDesc;
- Datum value[SEQ_COL_LASTCOL];
- bool null[SEQ_COL_LASTCOL];
- List *elts = NIL;
- List *atcmds = NIL;
- ListCell *lc;
Datum pgs_values[Natts_pg_sequence];
bool pgs_nulls[Natts_pg_sequence];
- int i;
/*
* If if_not_exists was given and a relation with the same name already
@@ -174,39 +148,11 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
&seqform, &last_value, &reset_state, &is_called,
&need_seq_rewrite, &owned_by);
- /*
- * Create relation (and fill value[] and null[] for the tuple)
- */
- for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++)
- {
- ColumnDef *coldef = NULL;
-
- switch (i)
- {
- case SEQ_COL_LASTVAL:
- coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
- value[i - 1] = Int64GetDatumFast(last_value);
- break;
- case SEQ_COL_LOG:
- coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
- value[i - 1] = Int64GetDatum((int64) 0);
- break;
- case SEQ_COL_CALLED:
- coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid);
- value[i - 1] = BoolGetDatum(false);
- break;
- }
-
- coldef->is_not_null = true;
- null[i - 1] = false;
-
- elts = lappend(elts, coldef);
- }
-
stmt->relation = seq->sequence;
stmt->inhRelations = NIL;
stmt->constraints = NIL;
stmt->options = NIL;
+ stmt->accessMethod = seq->accessMethod ? pstrdup(seq->accessMethod) : NULL;
stmt->oncommit = ONCOMMIT_NOOP;
stmt->tablespacename = NULL;
stmt->if_not_exists = seq->if_not_exists;
@@ -215,35 +161,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
*/
stmt->tableElts = NIL;
+ /*
+ * Initial relation has no attributes, these can be added later via the
+ * "init" AM callback.
+ */
+ stmt->tableElts = NIL;
+
address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
seqoid = address.objectId;
Assert(seqoid != InvalidOid);
rel = sequence_open(seqoid, AccessExclusiveLock);
- /* Add all the attributes to the sequence */
- foreach(lc, elts)
- {
- AlterTableCmd *atcmd;
-
- atcmd = makeNode(AlterTableCmd);
- atcmd->subtype = AT_AddColumnToSequence;
- atcmd->def = (Node *) lfirst(lc);
- atcmds = lappend(atcmds, atcmd);
- }
-
- /*
- * No recursion needed. Note that EventTriggerAlterTableStart() should
- * have been called.
- */
- AlterTableInternal(RelationGetRelid(rel), atcmds, false);
- CommandCounterIncrement();
-
- tupDesc = RelationGetDescr(rel);
-
- /* now initialize the sequence's data */
- tuple = heap_form_tuple(tupDesc, value, null);
- fill_seq_with_data(rel, tuple);
+ /* now initialize the sequence table structure and its data */
+ sequence_init(rel, last_value, is_called);
/* process OWNED BY if given */
if (owned_by)
@@ -292,10 +223,6 @@ ResetSequence(Oid seq_relid)
{
Relation seq_rel;
SeqTable elm;
- Form_pg_sequence_data seq;
- Buffer buf;
- HeapTupleData seqdatatuple;
- HeapTuple tuple;
HeapTuple pgstuple;
Form_pg_sequence pgsform;
int64 startv;
@@ -306,7 +233,6 @@ ResetSequence(Oid seq_relid)
* indeed a sequence.
*/
init_sequence(seq_relid, &elm, &seq_rel);
- (void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
if (!HeapTupleIsValid(pgstuple))
@@ -315,40 +241,8 @@ ResetSequence(Oid seq_relid)
startv = pgsform->seqstart;
ReleaseSysCache(pgstuple);
- /*
- * Copy the existing sequence tuple.
- */
- tuple = heap_copytuple(&seqdatatuple);
-
- /* Now we're done with the old page */
- UnlockReleaseBuffer(buf);
-
- /*
- * Modify the copied tuple to execute the restart (compare the RESTART
- * action in AlterSequence)
- */
- seq = (Form_pg_sequence_data) GETSTRUCT(tuple);
- seq->last_value = startv;
- seq->is_called = false;
- seq->log_cnt = 0;
-
- /*
- * Create a new storage file for the sequence.
- */
- RelationSetNewRelfilenumber(seq_rel, seq_rel->rd_rel->relpersistence);
-
- /*
- * Ensure sequence's relfrozenxid is at 0, since it won't contain any
- * unfrozen XIDs. Same with relminmxid, since a sequence will never
- * contain multixacts.
- */
- Assert(seq_rel->rd_rel->relfrozenxid == InvalidTransactionId);
- Assert(seq_rel->rd_rel->relminmxid == InvalidMultiXactId);
-
- /*
- * Insert the modified tuple into the new storage file.
- */
- fill_seq_with_data(seq_rel, tuple);
+ /* Sequence state is forcibly reset here. */
+ sequence_reset(seq_rel, startv, false, true);
/* Clear local cache so that we don't think we have cached numbers */
/* Note that we do not change the currval() state */
@@ -357,106 +251,6 @@ ResetSequence(Oid seq_relid)
sequence_close(seq_rel, NoLock);
}
-/*
- * Initialize a sequence's relation with the specified tuple as content
- *
- * This handles unlogged sequences by writing to both the main and the init
- * fork as necessary.
- */
-static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
-{
- fill_seq_fork_with_data(rel, tuple, MAIN_FORKNUM);
-
- if (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
- {
- SMgrRelation srel;
-
- srel = smgropen(rel->rd_locator, InvalidBackendId);
- smgrcreate(srel, INIT_FORKNUM, false);
- log_smgrcreate(&rel->rd_locator, INIT_FORKNUM);
- fill_seq_fork_with_data(rel, tuple, INIT_FORKNUM);
- FlushRelationBuffers(rel);
- smgrclose(srel);
- }
-}
-
-/*
- * Initialize a sequence's relation fork with the specified tuple as content
- */
-static void
-fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum)
-{
- Buffer buf;
- Page page;
- sequence_magic *sm;
- OffsetNumber offnum;
-
- /* Initialize first page of relation with special magic number */
-
- buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL,
- EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK);
- Assert(BufferGetBlockNumber(buf) == 0);
-
- page = BufferGetPage(buf);
-
- PageInit(page, BufferGetPageSize(buf), sizeof(sequence_magic));
- sm = (sequence_magic *) PageGetSpecialPointer(page);
- sm->magic = SEQ_MAGIC;
-
- /* Now insert sequence tuple */
-
- /*
- * Since VACUUM does not process sequences, we have to force the tuple to
- * have xmin = FrozenTransactionId now. Otherwise it would become
- * invisible to SELECTs after 2G transactions. It is okay to do this
- * because if the current transaction aborts, no other xact will ever
- * examine the sequence tuple anyway.
- */
- HeapTupleHeaderSetXmin(tuple->t_data, FrozenTransactionId);
- HeapTupleHeaderSetXminFrozen(tuple->t_data);
- HeapTupleHeaderSetCmin(tuple->t_data, FirstCommandId);
- HeapTupleHeaderSetXmax(tuple->t_data, InvalidTransactionId);
- tuple->t_data->t_infomask |= HEAP_XMAX_INVALID;
- ItemPointerSet(&tuple->t_data->t_ctid, 0, FirstOffsetNumber);
-
- /* check the comment above nextval_internal()'s equivalent call. */
- if (RelationNeedsWAL(rel))
- GetTopTransactionId();
-
- START_CRIT_SECTION();
-
- MarkBufferDirty(buf);
-
- offnum = PageAddItem(page, (Item) tuple->t_data, tuple->t_len,
- InvalidOffsetNumber, false, false);
- if (offnum != FirstOffsetNumber)
- elog(ERROR, "failed to add sequence tuple to page");
-
- /* XLOG stuff */
- if (RelationNeedsWAL(rel) || forkNum == INIT_FORKNUM)
- {
- xl_seq_rec xlrec;
- XLogRecPtr recptr;
-
- XLogBeginInsert();
- XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
-
- xlrec.locator = rel->rd_locator;
-
- XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec));
- XLogRegisterData((char *) tuple->t_data, tuple->t_len);
-
- recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG);
-
- PageSetLSN(page, recptr);
- }
-
- END_CRIT_SECTION();
-
- UnlockReleaseBuffer(buf);
-}
-
/*
* AlterSequence
*
@@ -468,10 +262,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
Oid relid;
SeqTable elm;
Relation seqrel;
- Buffer buf;
- HeapTupleData datatuple;
Form_pg_sequence seqform;
- Form_pg_sequence_data newdataform;
bool need_seq_rewrite;
List *owned_by;
ObjectAddress address;
@@ -480,7 +271,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
bool reset_state = false;
bool is_called;
int64 last_value;
- HeapTuple newdatatuple;
/* Open and lock sequence, and check for ownership along the way. */
relid = RangeVarGetRelidExtended(stmt->sequence,
@@ -507,16 +297,8 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
- /* lock page buffer and read tuple into new sequence structure */
- (void) read_seq_tuple(seqrel, &buf, &datatuple);
-
- /* copy the existing sequence data tuple, so it can be modified locally */
- newdatatuple = heap_copytuple(&datatuple);
- newdataform = (Form_pg_sequence_data) GETSTRUCT(newdatatuple);
- last_value = newdataform->last_value;
- is_called = newdataform->is_called;
-
- UnlockReleaseBuffer(buf);
+ /* Read sequence data */
+ sequence_get_state(seqrel, &last_value, &is_called);
/* Check and set new values */
init_params(pstate, stmt->options, stmt->for_identity, false,
@@ -526,32 +308,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
/* If needed, rewrite the sequence relation itself */
if (need_seq_rewrite)
{
- /* check the comment above nextval_internal()'s equivalent call. */
if (RelationNeedsWAL(seqrel))
GetTopTransactionId();
- /*
- * Create a new storage file for the sequence, making the state
- * changes transactional.
- */
- RelationSetNewRelfilenumber(seqrel, seqrel->rd_rel->relpersistence);
-
- /*
- * Ensure sequence's relfrozenxid is at 0, since it won't contain any
- * unfrozen XIDs. Same with relminmxid, since a sequence will never
- * contain multixacts.
- */
- Assert(seqrel->rd_rel->relfrozenxid == InvalidTransactionId);
- Assert(seqrel->rd_rel->relminmxid == InvalidMultiXactId);
-
- /*
- * Insert the modified tuple into the new storage file.
- */
- newdataform->last_value = last_value;
- newdataform->is_called = is_called;
- if (reset_state)
- newdataform->log_cnt = 0;
- fill_seq_with_data(seqrel, newdatatuple);
+ sequence_reset(seqrel, last_value, is_called, reset_state);
}
/* Clear local cache so that we don't think we have cached numbers */
@@ -580,8 +340,6 @@ SequenceChangePersistence(Oid relid, char newrelpersistence)
{
SeqTable elm;
Relation seqrel;
- Buffer buf;
- HeapTupleData seqdatatuple;
init_sequence(relid, &elm, &seqrel);
@@ -589,10 +347,7 @@ SequenceChangePersistence(Oid relid, char newrelpersistence)
if (RelationNeedsWAL(seqrel))
GetTopTransactionId();
- (void) read_seq_tuple(seqrel, &buf, &seqdatatuple);
- RelationSetNewRelfilenumber(seqrel, newrelpersistence);
- fill_seq_with_data(seqrel, &seqdatatuple);
- UnlockReleaseBuffer(buf);
+ sequence_change_persistence(seqrel, newrelpersistence);
sequence_close(seqrel, NoLock);
}
@@ -655,24 +410,15 @@ nextval_internal(Oid relid, bool check_permissions)
{
SeqTable elm;
Relation seqrel;
- Buffer buf;
- Page page;
HeapTuple pgstuple;
Form_pg_sequence pgsform;
- HeapTupleData seqdatatuple;
- Form_pg_sequence_data seq;
int64 incby,
maxv,
minv,
cache,
- log,
- fetch,
last;
- int64 result,
- next,
- rescnt = 0;
+ int64 result;
bool cycle;
- bool logit = false;
/* open and lock sequence */
init_sequence(relid, &elm, &seqrel);
@@ -717,105 +463,9 @@ nextval_internal(Oid relid, bool check_permissions)
cycle = pgsform->seqcycle;
ReleaseSysCache(pgstuple);
- /* lock page buffer and read tuple */
- seq = read_seq_tuple(seqrel, &buf, &seqdatatuple);
- page = BufferGetPage(buf);
-
- last = next = result = seq->last_value;
- fetch = cache;
- log = seq->log_cnt;
-
- if (!seq->is_called)
- {
- rescnt++; /* return last_value if not is_called */
- fetch--;
- }
-
- /*
- * Decide whether we should emit a WAL log record. If so, force up the
- * fetch count to grab SEQ_LOG_VALS more values than we actually need to
- * cache. (These will then be usable without logging.)
- *
- * If this is the first nextval after a checkpoint, we must force a new
- * WAL record to be written anyway, else replay starting from the
- * checkpoint would fail to advance the sequence past the logged values.
- * In this case we may as well fetch extra values.
- */
- if (log < fetch || !seq->is_called)
- {
- /* forced log to satisfy local demand for values */
- fetch = log = fetch + SEQ_LOG_VALS;
- logit = true;
- }
- else
- {
- XLogRecPtr redoptr = GetRedoRecPtr();
-
- if (PageGetLSN(page) <= redoptr)
- {
- /* last update of seq was before checkpoint */
- fetch = log = fetch + SEQ_LOG_VALS;
- logit = true;
- }
- }
-
- while (fetch) /* try to fetch cache [+ log ] numbers */
- {
- /*
- * Check MAXVALUE for ascending sequences and MINVALUE for descending
- * sequences
- */
- if (incby > 0)
- {
- /* ascending sequence */
- if ((maxv >= 0 && next > maxv - incby) ||
- (maxv < 0 && next + incby > maxv))
- {
- if (rescnt > 0)
- break; /* stop fetching */
- if (!cycle)
- ereport(ERROR,
- (errcode(ERRCODE_SEQUENCE_GENERATOR_LIMIT_EXCEEDED),
- errmsg("nextval: reached maximum value of sequence \"%s\" (%lld)",
- RelationGetRelationName(seqrel),
- (long long) maxv)));
- next = minv;
- }
- else
- next += incby;
- }
- else
- {
- /* descending sequence */
- if ((minv < 0 && next < minv - incby) ||
- (minv >= 0 && next + incby < minv))
- {
- if (rescnt > 0)
- break; /* stop fetching */
- if (!cycle)
- ereport(ERROR,
- (errcode(ERRCODE_SEQUENCE_GENERATOR_LIMIT_EXCEEDED),
- errmsg("nextval: reached minimum value of sequence \"%s\" (%lld)",
- RelationGetRelationName(seqrel),
- (long long) minv)));
- next = maxv;
- }
- else
- next += incby;
- }
- fetch--;
- if (rescnt < cache)
- {
- log--;
- rescnt++;
- last = next;
- if (rescnt == 1) /* if it's first result - */
- result = next; /* it's what to return */
- }
- }
-
- log -= fetch; /* adjust for any unfetched numbers */
- Assert(log >= 0);
+ /* retrieve next value from the access method */
+ result = sequence_nextval(seqrel, incby, maxv, minv, cache, cycle,
+ &last);
/* save info in local cache */
elm->increment = incby;
@@ -825,69 +475,6 @@ nextval_internal(Oid relid, bool check_permissions)
last_used_seq = elm;
- /*
- * If something needs to be WAL logged, acquire an xid, so this
- * transaction's commit will trigger a WAL flush and wait for syncrep.
- * It's sufficient to ensure the toplevel transaction has an xid, no need
- * to assign xids subxacts, that'll already trigger an appropriate wait.
- * (Have to do that here, so we're outside the critical section)
- */
- if (logit && RelationNeedsWAL(seqrel))
- GetTopTransactionId();
-
- /* ready to change the on-disk (or really, in-buffer) tuple */
- START_CRIT_SECTION();
-
- /*
- * We must mark the buffer dirty before doing XLogInsert(); see notes in
- * SyncOneBuffer(). However, we don't apply the desired changes just yet.
- * This looks like a violation of the buffer update protocol, but it is in
- * fact safe because we hold exclusive lock on the buffer. Any other
- * process, including a checkpoint, that tries to examine the buffer
- * contents will block until we release the lock, and then will see the
- * final state that we install below.
- */
- MarkBufferDirty(buf);
-
- /* XLOG stuff */
- if (logit && RelationNeedsWAL(seqrel))
- {
- xl_seq_rec xlrec;
- XLogRecPtr recptr;
-
- /*
- * We don't log the current state of the tuple, but rather the state
- * as it would appear after "log" more fetches. This lets us skip
- * that many future WAL records, at the cost that we lose those
- * sequence values if we crash.
- */
- XLogBeginInsert();
- XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
-
- /* set values that will be saved in xlog */
- seq->last_value = next;
- seq->is_called = true;
- seq->log_cnt = 0;
-
- xlrec.locator = seqrel->rd_locator;
-
- XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec));
- XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len);
-
- recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG);
-
- PageSetLSN(page, recptr);
- }
-
- /* Now update sequence tuple to the intended final state */
- seq->last_value = last; /* last fetched number */
- seq->is_called = true;
- seq->log_cnt = log; /* how much is logged */
-
- END_CRIT_SECTION();
-
- UnlockReleaseBuffer(buf);
-
sequence_close(seqrel, NoLock);
return result;
@@ -977,9 +564,6 @@ do_setval(Oid relid, int64 next, bool iscalled)
{
SeqTable elm;
Relation seqrel;
- Buffer buf;
- HeapTupleData seqdatatuple;
- Form_pg_sequence_data seq;
HeapTuple pgstuple;
Form_pg_sequence pgsform;
int64 maxv,
@@ -1013,9 +597,6 @@ do_setval(Oid relid, int64 next, bool iscalled)
*/
PreventCommandIfParallelMode("setval()");
- /* lock page buffer and read tuple */
- seq = read_seq_tuple(seqrel, &buf, &seqdatatuple);
-
if ((next < minv) || (next > maxv))
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
@@ -1037,37 +618,8 @@ do_setval(Oid relid, int64 next, bool iscalled)
if (RelationNeedsWAL(seqrel))
GetTopTransactionId();
- /* ready to change the on-disk (or really, in-buffer) tuple */
- START_CRIT_SECTION();
-
- seq->last_value = next; /* last fetched number */
- seq->is_called = iscalled;
- seq->log_cnt = 0;
-
- MarkBufferDirty(buf);
-
- /* XLOG stuff */
- if (RelationNeedsWAL(seqrel))
- {
- xl_seq_rec xlrec;
- XLogRecPtr recptr;
- Page page = BufferGetPage(buf);
-
- XLogBeginInsert();
- XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
-
- xlrec.locator = seqrel->rd_locator;
- XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec));
- XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len);
-
- recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG);
-
- PageSetLSN(page, recptr);
- }
-
- END_CRIT_SECTION();
-
- UnlockReleaseBuffer(buf);
+ /* Call the access method callback */
+ sequence_setval(seqrel, next, iscalled);
sequence_close(seqrel, NoLock);
}
@@ -1208,62 +760,6 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
}
-/*
- * Given an opened sequence relation, lock the page buffer and find the tuple
- *
- * *buf receives the reference to the pinned-and-ex-locked buffer
- * *seqdatatuple receives the reference to the sequence tuple proper
- * (this arg should point to a local variable of type HeapTupleData)
- *
- * Function's return value points to the data payload of the tuple
- */
-static Form_pg_sequence_data
-read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple)
-{
- Page page;
- ItemId lp;
- sequence_magic *sm;
- Form_pg_sequence_data seq;
-
- *buf = ReadBuffer(rel, 0);
- LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
-
- page = BufferGetPage(*buf);
- sm = (sequence_magic *) PageGetSpecialPointer(page);
-
- if (sm->magic != SEQ_MAGIC)
- elog(ERROR, "bad magic number in sequence \"%s\": %08X",
- RelationGetRelationName(rel), sm->magic);
-
- lp = PageGetItemId(page, FirstOffsetNumber);
- Assert(ItemIdIsNormal(lp));
-
- /* Note we currently only bother to set these two fields of *seqdatatuple */
- seqdatatuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
- seqdatatuple->t_len = ItemIdGetLength(lp);
-
- /*
- * Previous releases of Postgres neglected to prevent SELECT FOR UPDATE on
- * a sequence, which would leave a non-frozen XID in the sequence tuple's
- * xmax, which eventually leads to clog access failures or worse. If we
- * see this has happened, clean up after it. We treat this like a hint
- * bit update, ie, don't bother to WAL-log it, since we can certainly do
- * this again if the update gets lost.
- */
- Assert(!(seqdatatuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI));
- if (HeapTupleHeaderGetRawXmax(seqdatatuple->t_data) != InvalidTransactionId)
- {
- HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId);
- seqdatatuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED;
- seqdatatuple->t_data->t_infomask |= HEAP_XMAX_INVALID;
- MarkBufferDirtyHint(*buf, true);
- }
-
- seq = (Form_pg_sequence_data) GETSTRUCT(seqdatatuple);
-
- return seq;
-}
-
/*
* init_params: process the options list of CREATE or ALTER SEQUENCE, and
* store the values into appropriate fields of seqform, for changes that go
@@ -1589,7 +1085,7 @@ init_params(ParseState *pstate, List *options, bool for_identity,
errmsg("RESTART value (%lld) cannot be less than MINVALUE (%lld)",
(long long) *last_value,
(long long) seqform->seqmin)));
- if (*last_value > seqform->seqmax) //here
+ if (*last_value > seqform->seqmax)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("RESTART value (%lld) cannot be greater than MAXVALUE (%lld)",
@@ -1823,9 +1319,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
SeqTable elm;
Relation seqrel;
TupleDesc tupdesc;
- Buffer buf;
- HeapTupleData seqtuple;
- Form_pg_sequence_data seq;
bool is_called;
int64 last_value;
@@ -1842,12 +1335,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
errmsg("permission denied for sequence %s",
RelationGetRelationName(seqrel))));
- seq = read_seq_tuple(seqrel, &buf, &seqtuple);
-
- is_called = seq->is_called;
- last_value = seq->last_value;
-
- UnlockReleaseBuffer(buf);
+ sequence_get_state(seqrel, &last_value, &is_called);
sequence_close(seqrel, NoLock);
values[0] = BoolGetDatum(is_called);
@@ -1855,57 +1343,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
}
-
-void
-seq_redo(XLogReaderState *record)
-{
- XLogRecPtr lsn = record->EndRecPtr;
- uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
- Buffer buffer;
- Page page;
- Page localpage;
- char *item;
- Size itemsz;
- xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
- sequence_magic *sm;
-
- if (info != XLOG_SEQ_LOG)
- elog(PANIC, "seq_redo: unknown op code %u", info);
-
- buffer = XLogInitBufferForRedo(record, 0);
- page = (Page) BufferGetPage(buffer);
-
- /*
- * We always reinit the page. However, since this WAL record type is also
- * used for updating sequences, it's possible that a hot-standby backend
- * is examining the page concurrently; so we mustn't transiently trash the
- * buffer. The solution is to build the correct new page contents in
- * local workspace and then memcpy into the buffer. Then only bytes that
- * are supposed to change will change, even transiently. We must palloc
- * the local page for alignment reasons.
- */
- localpage = (Page) palloc(BufferGetPageSize(buffer));
-
- PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
- sm = (sequence_magic *) PageGetSpecialPointer(localpage);
- sm->magic = SEQ_MAGIC;
-
- item = (char *) xlrec + sizeof(xl_seq_rec);
- itemsz = XLogRecGetDataLen(record) - sizeof(xl_seq_rec);
-
- if (PageAddItem(localpage, (Item) item, itemsz,
- FirstOffsetNumber, false, false) == InvalidOffsetNumber)
- elog(PANIC, "seq_redo: failed to add item to page");
-
- PageSetLSN(localpage, lsn);
-
- memcpy(page, localpage, BufferGetPageSize(buffer));
- MarkBufferDirty(buffer);
- UnlockReleaseBuffer(buffer);
-
- pfree(localpage);
-}
-
/*
* Flush cached sequence information.
*/
@@ -1920,14 +1357,3 @@ ResetSequenceCaches(void)
last_used_seq = NULL;
}
-
-/*
- * Mask a Sequence page before performing consistency checks on it.
- */
-void
-seq_mask(char *page, BlockNumber blkno)
-{
- mask_page_lsn_and_checksum(page);
-
- mask_unused_space(page);
-}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b23c792f37..76ce4596b0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -22,6 +22,7 @@
#include "access/reloptions.h"
#include "access/relscan.h"
#include "access/sysattr.h"
+#include "access/sequenceam.h"
#include "access/tableam.h"
#include "access/toast_compression.h"
#include "access/xact.h"
@@ -957,10 +958,17 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
}
else if (RELKIND_HAS_TABLE_AM(relkind))
accessMethod = default_table_access_method;
+ else if (relkind == RELKIND_SEQUENCE)
+ accessMethod = default_sequence_access_method;
- /* look up the access method, verify it is for a table */
+ /* look up the access method, verify it is for a table or a sequence */
if (accessMethod != NULL)
- accessMethodId = get_table_am_oid(accessMethod, false);
+ {
+ if (relkind == RELKIND_SEQUENCE)
+ accessMethodId = get_sequence_am_oid(accessMethod, false);
+ else
+ accessMethodId = get_table_am_oid(accessMethod, false);
+ }
/*
* Create the relation. Inherited defaults and constraints are passed in
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index ebbe9052cb..31dfd9c233 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -48,6 +48,7 @@ node_headers = \
nodes/execnodes.h \
access/amapi.h \
access/sdir.h \
+ access/sequenceam.h \
access/tableam.h \
access/tsmapi.h \
commands/event_trigger.h \
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 72c7963578..b642eca278 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -59,6 +59,7 @@ my @all_input_files = qw(
nodes/execnodes.h
access/amapi.h
access/sdir.h
+ access/sequenceam.h
access/tableam.h
access/tsmapi.h
commands/event_trigger.h
@@ -83,6 +84,7 @@ my @nodetag_only_files = qw(
nodes/execnodes.h
access/amapi.h
access/sdir.h
+ access/sequenceam.h
access/tableam.h
access/tsmapi.h
commands/event_trigger.h
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d631ac89a9..23c06daca6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -388,6 +388,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> copy_file_name
access_method_clause attr_name
table_access_method_clause name cursor_name file_name
+ sequence_access_method_clause
cluster_index_specification
%type <list> func_name handler_name qual_Op qual_all_Op subquery_Op
@@ -4753,23 +4754,26 @@ RefreshMatViewStmt:
CreateSeqStmt:
CREATE OptTemp SEQUENCE qualified_name OptSeqOptList
+ sequence_access_method_clause
{
CreateSeqStmt *n = makeNode(CreateSeqStmt);
-
$4->relpersistence = $2;
n->sequence = $4;
n->options = $5;
+ n->accessMethod = $6;
n->ownerId = InvalidOid;
n->if_not_exists = false;
$$ = (Node *) n;
}
| CREATE OptTemp SEQUENCE IF_P NOT EXISTS qualified_name OptSeqOptList
+ sequence_access_method_clause
{
CreateSeqStmt *n = makeNode(CreateSeqStmt);
$7->relpersistence = $2;
n->sequence = $7;
n->options = $8;
+ n->accessMethod = $9;
n->ownerId = InvalidOid;
n->if_not_exists = true;
$$ = (Node *) n;
@@ -4806,6 +4810,11 @@ OptParenthesizedSeqOptList: '(' SeqOptList ')' { $$ = $2; }
| /*EMPTY*/ { $$ = NIL; }
;
+sequence_access_method_clause:
+ USING name { $$ = $2; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
SeqOptList: SeqOptElem { $$ = list_make1($1); }
| SeqOptList SeqOptElem { $$ = lappend($1, $2); }
;
@@ -5802,6 +5811,7 @@ CreateAmStmt: CREATE ACCESS METHOD name TYPE_P am_type HANDLER handler_name
am_type:
INDEX { $$ = AMTYPE_INDEX; }
+ | SEQUENCE { $$ = AMTYPE_SEQUENCE; }
| TABLE { $$ = AMTYPE_TABLE; }
;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index cf0d432ab1..1e1633a01c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -26,6 +26,7 @@
#include "access/htup_details.h"
#include "access/relation.h"
#include "access/reloptions.h"
+#include "access/sequenceam.h"
#include "access/table.h"
#include "access/toast_compression.h"
#include "catalog/dependency.h"
@@ -461,6 +462,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
seqstmt->options = seqoptions;
+ seqstmt->accessMethod = NULL;
/*
* If a sequence data type was specified, add it to the options. Prepend
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 3ba8cb192c..15e065d755 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -372,6 +372,7 @@ PSEUDOTYPE_DUMMY_IO_FUNCS(language_handler);
PSEUDOTYPE_DUMMY_IO_FUNCS(fdw_handler);
PSEUDOTYPE_DUMMY_IO_FUNCS(table_am_handler);
PSEUDOTYPE_DUMMY_IO_FUNCS(index_am_handler);
+PSEUDOTYPE_DUMMY_IO_FUNCS(sequence_am_handler);
PSEUDOTYPE_DUMMY_IO_FUNCS(tsm_handler);
PSEUDOTYPE_DUMMY_IO_FUNCS(internal);
PSEUDOTYPE_DUMMY_IO_FUNCS(anyelement);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index b3faccbefe..8504bb52f7 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -35,6 +35,7 @@
#include "access/nbtree.h"
#include "access/parallel.h"
#include "access/reloptions.h"
+#include "access/sequenceam.h"
#include "access/sysattr.h"
#include "access/table.h"
#include "access/tableam.h"
@@ -66,6 +67,7 @@
#include "catalog/pg_type.h"
#include "catalog/schemapg.h"
#include "catalog/storage.h"
+#include "commands/defrem.h"
#include "commands/policy.h"
#include "commands/publicationcmds.h"
#include "commands/trigger.h"
@@ -301,6 +303,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
static void RelationBuildTupleDesc(Relation relation);
static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
static void RelationInitPhysicalAddr(Relation relation);
+static void RelationInitSequenceAccessMethod(Relation relation);
static void load_critical_index(Oid indexoid, Oid heapoid);
static TupleDesc GetPgClassDescriptor(void);
static TupleDesc GetPgIndexDescriptor(void);
@@ -1206,9 +1209,10 @@ retry:
if (relation->rd_rel->relkind == RELKIND_INDEX ||
relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
RelationInitIndexAccessInfo(relation);
- else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) ||
- relation->rd_rel->relkind == RELKIND_SEQUENCE)
+ else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))
RelationInitTableAccessMethod(relation);
+ else if (relation->rd_rel->relkind == RELKIND_SEQUENCE)
+ RelationInitSequenceAccessMethod(relation);
else
Assert(relation->rd_rel->relam == InvalidOid);
@@ -1805,17 +1809,7 @@ RelationInitTableAccessMethod(Relation relation)
HeapTuple tuple;
Form_pg_am aform;
- if (relation->rd_rel->relkind == RELKIND_SEQUENCE)
- {
- /*
- * Sequences are currently accessed like heap tables, but it doesn't
- * seem prudent to show that in the catalog. So just overwrite it
- * here.
- */
- Assert(relation->rd_rel->relam == InvalidOid);
- relation->rd_amhandler = F_HEAP_TABLEAM_HANDLER;
- }
- else if (IsCatalogRelation(relation))
+ if (IsCatalogRelation(relation))
{
/*
* Avoid doing a syscache lookup for catalog tables.
@@ -1846,6 +1840,47 @@ RelationInitTableAccessMethod(Relation relation)
InitTableAmRoutine(relation);
}
+/*
+ * Initialize sequence-access-method support data for a sequence relation
+ */
+static void
+RelationInitSequenceAccessMethod(Relation relation)
+{
+ HeapTuple tuple;
+ Form_pg_am aform;
+ const char *tableam_name;
+ Oid tableam_oid;
+ Oid tableam_handler;
+
+ /*
+ * Look up the sequence access method, save the OID of its handler
+ * function.
+ */
+ Assert(relation->rd_rel->relam != InvalidOid);
+ relation->rd_amhandler = GetSequenceAmRoutineId(relation->rd_rel->relam);
+
+ /*
+ * Now we can fetch the sequence AM's API struct.
+ */
+ relation->rd_sequenceam = GetSequenceAmRoutine(relation->rd_amhandler);
+
+ /*
+ * From the sequence AM, set its expected table access method.
+ */
+ tableam_name = sequence_get_table_am(relation);
+ tableam_oid = get_table_am_oid(tableam_name, false);
+
+ tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(tableam_oid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for access method %u",
+ tableam_oid);
+ aform = (Form_pg_am) GETSTRUCT(tuple);
+ tableam_handler = aform->amhandler;
+ ReleaseSysCache(tuple);
+
+ relation->rd_tableam = GetTableAmRoutine(tableam_handler);
+}
+
/*
* formrdesc
*
@@ -3687,14 +3722,17 @@ RelationBuildLocalRelation(const char *relname,
rel->rd_rel->relam = accessmtd;
/*
- * RelationInitTableAccessMethod will do syscache lookups, so we mustn't
- * run it in CacheMemoryContext. Fortunately, the remaining steps don't
- * require a long-lived current context.
+ * RelationInitTableAccessMethod() and RelationInitSequenceAccessMethod()
+ * will do syscache lookups, so we mustn't run them in CacheMemoryContext.
+ * Fortunately, the remaining steps don't require a long-lived current
+ * context.
*/
MemoryContextSwitchTo(oldcxt);
- if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_SEQUENCE)
+ if (RELKIND_HAS_TABLE_AM(relkind))
RelationInitTableAccessMethod(rel);
+ else if (relkind == RELKIND_SEQUENCE)
+ RelationInitSequenceAccessMethod(rel);
/*
* Okay to insert into the relcache hash table.
@@ -4307,13 +4345,21 @@ RelationCacheInitializePhase3(void)
/* Reload tableam data if needed */
if (relation->rd_tableam == NULL &&
- (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || relation->rd_rel->relkind == RELKIND_SEQUENCE))
+ (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind)))
{
RelationInitTableAccessMethod(relation);
Assert(relation->rd_tableam != NULL);
restart = true;
}
+ else if (relation->rd_sequenceam == NULL &&
+ relation->rd_rel->relkind == RELKIND_SEQUENCE)
+ {
+ RelationInitSequenceAccessMethod(relation);
+ Assert(relation->rd_sequenceam != NULL);
+
+ restart = true;
+ }
/* Release hold on the relation */
RelationDecrementReferenceCount(relation);
@@ -6313,8 +6359,10 @@ load_relcache_init_file(bool shared)
nailed_rels++;
/* Load table AM data */
- if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind) || rel->rd_rel->relkind == RELKIND_SEQUENCE)
+ if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind))
RelationInitTableAccessMethod(rel);
+ else if (rel->rd_rel->relkind == RELKIND_SEQUENCE)
+ RelationInitSequenceAccessMethod(rel);
Assert(rel->rd_index == NULL);
Assert(rel->rd_indextuple == NULL);
@@ -6326,6 +6374,7 @@ load_relcache_init_file(bool shared)
Assert(rel->rd_supportinfo == NULL);
Assert(rel->rd_indoption == NULL);
Assert(rel->rd_indcollation == NULL);
+ Assert(rel->rd_sequenceam == NULL);
Assert(rel->rd_opcoptions == NULL);
}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 6474e35ec0..6d54deda8d 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -29,6 +29,7 @@
#include "access/commit_ts.h"
#include "access/gin.h"
#include "access/toast_compression.h"
+#include "access/sequenceam.h"
#include "access/twophase.h"
#include "access/xlog_internal.h"
#include "access/xlogprefetcher.h"
@@ -3999,6 +4000,17 @@ struct config_string ConfigureNamesString[] =
check_default_table_access_method, NULL, NULL
},
+ {
+ {"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("Sets the default sequence access method for new sequences."),
+ NULL,
+ GUC_IS_NAME
+ },
+ &default_sequence_access_method,
+ DEFAULT_SEQUENCE_ACCESS_METHOD,
+ check_default_sequence_access_method, NULL, NULL
+ },
+
{
{"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT,
gettext_noop("Sets the default tablespace to create tables and indexes in."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf9f283cfe..e3e46923cf 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -684,6 +684,7 @@
# error
#search_path = '"$user", public' # schema names
#row_security = on
+#default_sequence_access_method = 'local'
#default_table_access_method = 'heap'
#default_tablespace = '' # a tablespace name, '' uses the default
#default_toast_compression = 'pglz' # 'pglz' or 'lz4'
diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore
index ec51f41c76..0f45509f2c 100644
--- a/src/bin/pg_waldump/.gitignore
+++ b/src/bin/pg_waldump/.gitignore
@@ -10,13 +10,13 @@
/gistdesc.c
/hashdesc.c
/heapdesc.c
+/localseqdesc.c
/logicalmsgdesc.c
/mxactdesc.c
/nbtdesc.c
/relmapdesc.c
/replorigindesc.c
/rmgrdesc_utils.c
-/seqdesc.c
/smgrdesc.c
/spgdesc.c
/standbydesc.c
diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c
index 6b8c17bb4c..ff09335607 100644
--- a/src/bin/pg_waldump/rmgrdesc.c
+++ b/src/bin/pg_waldump/rmgrdesc.c
@@ -16,6 +16,7 @@
#include "access/gistxlog.h"
#include "access/hash_xlog.h"
#include "access/heapam_xlog.h"
+#include "access/localam.h"
#include "access/multixact.h"
#include "access/nbtxlog.h"
#include "access/rmgr.h"
diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl
index 029a0d0521..7be589d92e 100644
--- a/src/bin/pg_waldump/t/001_basic.pl
+++ b/src/bin/pg_waldump/t/001_basic.pl
@@ -41,7 +41,7 @@ Btree
Hash
Gin
Gist
-Sequence
+LocalSequence
SPGist
BRIN
CommitTs
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 5077e7b358..aa8c6c73e6 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -161,10 +161,12 @@ describeAccessMethods(const char *pattern, bool verbose)
"SELECT amname AS \"%s\",\n"
" CASE amtype"
" WHEN 'i' THEN '%s'"
+ " WHEN 's' THEN '%s'"
" WHEN 't' THEN '%s'"
" END AS \"%s\"",
gettext_noop("Name"),
gettext_noop("Index"),
+ gettext_noop("Sequence"),
gettext_noop("Table"),
gettext_noop("Type"));
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 049801186c..752208b6a2 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2197,7 +2197,7 @@ psql_completion(const char *text, int start, int end)
else if (Matches("ALTER", "SEQUENCE", MatchAny))
COMPLETE_WITH("AS", "INCREMENT", "MINVALUE", "MAXVALUE", "RESTART",
"START", "NO", "CACHE", "CYCLE", "SET", "OWNED BY",
- "OWNER TO", "RENAME TO");
+ "OWNER TO", "RENAME TO", "USING");
/* ALTER SEQUENCE <name> AS */
else if (TailMatches("ALTER", "SEQUENCE", MatchAny, "AS"))
COMPLETE_WITH_CS("smallint", "integer", "bigint");
@@ -3204,7 +3204,7 @@ psql_completion(const char *text, int start, int end)
else if (TailMatches("CREATE", "SEQUENCE", MatchAny) ||
TailMatches("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny))
COMPLETE_WITH("AS", "INCREMENT BY", "MINVALUE", "MAXVALUE", "NO",
- "CACHE", "CYCLE", "OWNED BY", "START WITH");
+ "CACHE", "CYCLE", "OWNED BY", "START WITH", "USING");
else if (TailMatches("CREATE", "SEQUENCE", MatchAny, "AS") ||
TailMatches("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "AS"))
COMPLETE_WITH_CS("smallint", "integer", "bigint");
diff --git a/src/test/regress/expected/create_am.out b/src/test/regress/expected/create_am.out
index b50293d514..dfcc9cff49 100644
--- a/src/test/regress/expected/create_am.out
+++ b/src/test/regress/expected/create_am.out
@@ -163,11 +163,6 @@ CREATE VIEW tableam_view_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2;
ERROR: syntax error at or near "USING"
LINE 1: CREATE VIEW tableam_view_heap2 USING heap2 AS SELECT * FROM ...
^
--- CREATE SEQUENCE doesn't support USING
-CREATE SEQUENCE tableam_seq_heap2 USING heap2;
-ERROR: syntax error at or near "USING"
-LINE 1: CREATE SEQUENCE tableam_seq_heap2 USING heap2;
- ^
-- CREATE MATERIALIZED VIEW does support USING
CREATE MATERIALIZED VIEW tableam_tblmv_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2;
SELECT f1 FROM tableam_tblmv_heap2 ORDER BY f1;
@@ -331,9 +326,12 @@ CREATE TABLE tableam_parted_heapx (a text, b int) PARTITION BY list (a);
CREATE TABLE tableam_parted_1_heapx PARTITION OF tableam_parted_heapx FOR VALUES IN ('a', 'b');
-- but an explicitly set AM overrides it
CREATE TABLE tableam_parted_2_heapx PARTITION OF tableam_parted_heapx FOR VALUES IN ('c', 'd') USING heap;
--- sequences, views and foreign servers shouldn't have an AM
-CREATE VIEW tableam_view_heapx AS SELECT * FROM tableam_tbl_heapx;
+-- sequences have an AM
+SET LOCAL default_sequence_access_method = 'local';
CREATE SEQUENCE tableam_seq_heapx;
+RESET default_sequence_access_method;
+-- views and foreign servers shouldn't have an AM
+CREATE VIEW tableam_view_heapx AS SELECT * FROM tableam_tbl_heapx;
CREATE FOREIGN DATA WRAPPER fdw_heap2 VALIDATOR postgresql_fdw_validator;
CREATE SERVER fs_heap2 FOREIGN DATA WRAPPER fdw_heap2 ;
CREATE FOREIGN table tableam_fdw_heapx () SERVER fs_heap2;
@@ -356,7 +354,7 @@ ORDER BY 3, 1, 2;
r | heap2 | tableam_parted_1_heapx
r | heap | tableam_parted_2_heapx
p | | tableam_parted_heapx
- S | | tableam_seq_heapx
+ S | local | tableam_seq_heapx
r | heap2 | tableam_tbl_heapx
r | heap2 | tableam_tblas_heapx
m | heap2 | tableam_tblmv_heapx
@@ -388,3 +386,22 @@ table tableam_parted_b_heap2 depends on access method heap2
table tableam_parted_d_heap2 depends on access method heap2
HINT: Use DROP ... CASCADE to drop the dependent objects too.
-- we intentionally leave the objects created above alive, to verify pg_dump support
+-- Checks for sequence access methods
+-- Create new sequence access method which uses standard local handler
+CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER local_sequenceam_handler;
+-- Create and use sequence
+CREATE SEQUENCE test_seqam USING local2;
+SELECT nextval('test_seqam'::regclass);
+ nextval
+---------
+ 1
+(1 row)
+
+-- Try to drop and fail on dependency
+DROP ACCESS METHOD local2;
+ERROR: cannot drop access method local2 because other objects depend on it
+DETAIL: sequence test_seqam depends on access method local2
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+-- And cleanup
+DROP SEQUENCE test_seqam;
+DROP ACCESS METHOD local2;
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 7610b011d6..12f48e4beb 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1929,6 +1929,18 @@ WHERE p1.oid = a1.amhandler AND a1.amtype = 't' AND
-----+--------+-----+---------
(0 rows)
+-- check for sequence amhandler functions with the wrong signature
+SELECT a1.oid, a1.amname, p1.oid, p1.proname
+FROM pg_am AS a1, pg_proc AS p1
+WHERE p1.oid = a1.amhandler AND a1.amtype = 's' AND
+ (p1.prorettype != 'sequence_am_handler'::regtype
+ OR p1.proretset
+ OR p1.pronargs != 1
+ OR p1.proargtypes[0] != 'internal'::regtype);
+ oid | amname | oid | proname
+-----+--------+-----+---------
+(0 rows)
+
-- **************** pg_amop ****************
-- Look for illegal values in pg_amop fields
SELECT a1.amopfamily, a1.amopstrategy
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 13e4f6db7b..7987dd0586 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4958,8 +4958,8 @@ Indexes:
-- check printing info about access methods
\dA
List of access methods
- Name | Type
---------+-------
+ Name | Type
+--------+----------
brin | Index
btree | Index
gin | Index
@@ -4967,13 +4967,14 @@ List of access methods
hash | Index
heap | Table
heap2 | Table
+ local | Sequence
spgist | Index
-(8 rows)
+(9 rows)
\dA *
List of access methods
- Name | Type
---------+-------
+ Name | Type
+--------+----------
brin | Index
btree | Index
gin | Index
@@ -4981,8 +4982,9 @@ List of access methods
hash | Index
heap | Table
heap2 | Table
+ local | Sequence
spgist | Index
-(8 rows)
+(9 rows)
\dA h*
List of access methods
@@ -5007,32 +5009,34 @@ List of access methods
\dA: extra argument "bar" ignored
\dA+
- List of access methods
- Name | Type | Handler | Description
---------+-------+----------------------+----------------------------------------
- brin | Index | brinhandler | block range index (BRIN) access method
- btree | Index | bthandler | b-tree index access method
- gin | Index | ginhandler | GIN index access method
- gist | Index | gisthandler | GiST index access method
- hash | Index | hashhandler | hash index access method
- heap | Table | heap_tableam_handler | heap table access method
- heap2 | Table | heap_tableam_handler |
- spgist | Index | spghandler | SP-GiST index access method
-(8 rows)
+ List of access methods
+ Name | Type | Handler | Description
+--------+----------+--------------------------+----------------------------------------
+ brin | Index | brinhandler | block range index (BRIN) access method
+ btree | Index | bthandler | b-tree index access method
+ gin | Index | ginhandler | GIN index access method
+ gist | Index | gisthandler | GiST index access method
+ hash | Index | hashhandler | hash index access method
+ heap | Table | heap_tableam_handler | heap table access method
+ heap2 | Table | heap_tableam_handler |
+ local | Sequence | local_sequenceam_handler | local sequence access method
+ spgist | Index | spghandler | SP-GiST index access method
+(9 rows)
\dA+ *
- List of access methods
- Name | Type | Handler | Description
---------+-------+----------------------+----------------------------------------
- brin | Index | brinhandler | block range index (BRIN) access method
- btree | Index | bthandler | b-tree index access method
- gin | Index | ginhandler | GIN index access method
- gist | Index | gisthandler | GiST index access method
- hash | Index | hashhandler | hash index access method
- heap | Table | heap_tableam_handler | heap table access method
- heap2 | Table | heap_tableam_handler |
- spgist | Index | spghandler | SP-GiST index access method
-(8 rows)
+ List of access methods
+ Name | Type | Handler | Description
+--------+----------+--------------------------+----------------------------------------
+ brin | Index | brinhandler | block range index (BRIN) access method
+ btree | Index | bthandler | b-tree index access method
+ gin | Index | ginhandler | GIN index access method
+ gist | Index | gisthandler | GiST index access method
+ hash | Index | hashhandler | hash index access method
+ heap | Table | heap_tableam_handler | heap table access method
+ heap2 | Table | heap_tableam_handler |
+ local | Sequence | local_sequenceam_handler | local sequence access method
+ spgist | Index | spghandler | SP-GiST index access method
+(9 rows)
\dA+ h*
List of access methods
diff --git a/src/test/regress/sql/create_am.sql b/src/test/regress/sql/create_am.sql
index 2785ffd8bb..6b180519aa 100644
--- a/src/test/regress/sql/create_am.sql
+++ b/src/test/regress/sql/create_am.sql
@@ -117,9 +117,6 @@ SELECT INTO tableam_tblselectinto_heap2 USING heap2 FROM tableam_tbl_heap2;
-- CREATE VIEW doesn't support USING
CREATE VIEW tableam_view_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2;
--- CREATE SEQUENCE doesn't support USING
-CREATE SEQUENCE tableam_seq_heap2 USING heap2;
-
-- CREATE MATERIALIZED VIEW does support USING
CREATE MATERIALIZED VIEW tableam_tblmv_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2;
SELECT f1 FROM tableam_tblmv_heap2 ORDER BY f1;
@@ -222,9 +219,13 @@ CREATE TABLE tableam_parted_1_heapx PARTITION OF tableam_parted_heapx FOR VALUES
-- but an explicitly set AM overrides it
CREATE TABLE tableam_parted_2_heapx PARTITION OF tableam_parted_heapx FOR VALUES IN ('c', 'd') USING heap;
--- sequences, views and foreign servers shouldn't have an AM
-CREATE VIEW tableam_view_heapx AS SELECT * FROM tableam_tbl_heapx;
+-- sequences have an AM
+SET LOCAL default_sequence_access_method = 'local';
CREATE SEQUENCE tableam_seq_heapx;
+RESET default_sequence_access_method;
+
+-- views and foreign servers shouldn't have an AM
+CREATE VIEW tableam_view_heapx AS SELECT * FROM tableam_tbl_heapx;
CREATE FOREIGN DATA WRAPPER fdw_heap2 VALIDATOR postgresql_fdw_validator;
CREATE SERVER fs_heap2 FOREIGN DATA WRAPPER fdw_heap2 ;
CREATE FOREIGN table tableam_fdw_heapx () SERVER fs_heap2;
@@ -257,3 +258,16 @@ CREATE TABLE i_am_a_failure() USING "btree";
DROP ACCESS METHOD heap2;
-- we intentionally leave the objects created above alive, to verify pg_dump support
+
+-- Checks for sequence access methods
+
+-- Create new sequence access method which uses standard local handler
+CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER local_sequenceam_handler;
+-- Create and use sequence
+CREATE SEQUENCE test_seqam USING local2;
+SELECT nextval('test_seqam'::regclass);
+-- Try to drop and fail on dependency
+DROP ACCESS METHOD local2;
+-- And cleanup
+DROP SEQUENCE test_seqam;
+DROP ACCESS METHOD local2;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 2fe7b6dcc4..1409622374 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1229,6 +1229,16 @@ WHERE p1.oid = a1.amhandler AND a1.amtype = 't' AND
OR p1.pronargs != 1
OR p1.proargtypes[0] != 'internal'::regtype);
+-- check for sequence amhandler functions with the wrong signature
+
+SELECT a1.oid, a1.amname, p1.oid, p1.proname
+FROM pg_am AS a1, pg_proc AS p1
+WHERE p1.oid = a1.amhandler AND a1.amtype = 's' AND
+ (p1.prorettype != 'sequence_am_handler'::regtype
+ OR p1.proretset
+ OR p1.pronargs != 1
+ OR p1.proargtypes[0] != 'internal'::regtype);
+
-- **************** pg_amop ****************
-- Look for illegal values in pg_amop fields
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index d659adbfd6..7aa3ba978e 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2492,6 +2492,7 @@ SeqScan
SeqScanState
SeqTable
SeqTableData
+SequenceAmRoutine
SerCommitSeqNo
SerialControl
SerialIOData
@@ -3462,6 +3463,7 @@ lineno_t
list_sort_comparator
local_relopt
local_relopts
+local_sequence_magic
local_source
locale_t
locate_agg_of_level_context
@@ -3725,7 +3727,6 @@ save_buffer
scram_state
scram_state_enum
sem_t
-sequence_magic
set_join_pathlist_hook_type
set_rel_pathlist_hook_type
shm_mq
@@ -3942,6 +3943,7 @@ xl_heap_visible
xl_invalid_page
xl_invalid_page_key
xl_invalidations
+xl_local_seq_rec
xl_logical_message
xl_multi_insert_tuple
xl_multixact_create
@@ -3953,7 +3955,6 @@ xl_replorigin_drop
xl_replorigin_set
xl_restore_point
xl_running_xacts
-xl_seq_rec
xl_smgr_create
xl_smgr_truncate
xl_standby_lock
--
2.43.0