seqamv3.patch
text/x-diff
Patch
Format: unified
| File | + | − |
|---|---|---|
| src/backend/access/common/reloptions.c | 26 | 3 |
| src/backend/access/Makefile | 1 | 1 |
| src/backend/access/sequence/Makefile | 17 | 0 |
| src/backend/access/sequence/seqam.c | 351 | 0 |
| src/backend/catalog/Makefile | 1 | 1 |
| src/backend/commands/sequence.c | 567 | 205 |
| src/backend/commands/tablecmds.c | 3 | 0 |
| src/backend/nodes/copyfuncs.c | 4 | 0 |
| src/backend/nodes/equalfuncs.c | 4 | 0 |
| src/backend/parser/gram.y | 76 | 2 |
| src/backend/parser/parse_utilcmd.c | 1 | 0 |
| src/backend/storage/page/bufpage.c | 3 | 0 |
| src/backend/utils/cache/catcache.c | 4 | 2 |
| src/backend/utils/cache/relcache.c | 58 | 4 |
| src/backend/utils/cache/syscache.c | 23 | 0 |
| src/backend/utils/misc/guc.c | 12 | 0 |
| src/bin/pg_dump/pg_dump.c | 33 | 8 |
| src/bin/psql/describe.c | 60 | 29 |
| src/include/access/reloptions.h | 5 | 1 |
| src/include/access/seqam.h | 51 | 0 |
| src/include/catalog/indexing.h | 5 | 0 |
| src/include/catalog/pg_proc.h | 5 | 0 |
| src/include/catalog/pg_seqam.h | 74 | 0 |
| src/include/commands/sequence.h | 3 | 1 |
| src/include/nodes/parsenodes.h | 6 | 2 |
| src/include/utils/guc.h | 1 | 0 |
| src/include/utils/rel.h | 21 | 6 |
| src/include/utils/syscache.h | 2 | 0 |
| src/test/regress/expected/sanity_check.out | 1 | 0 |
| src/test/regress/expected/sequence.out | 6 | 6 |
| src/test/regress/expected/updatable_views.out | 2 | 1 |
diff --git a/src/backend/access/Makefile b/src/backend/access/Makefile
index c32088f..aea4a14 100644
--- a/src/backend/access/Makefile
+++ b/src/backend/access/Makefile
@@ -8,6 +8,6 @@ subdir = src/backend/access
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
-SUBDIRS = common gin gist hash heap index nbtree rmgrdesc spgist transam
+SUBDIRS = common gin gist hash heap index nbtree rmgrdesc spgist transam sequence
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index e0b81b9..c5b7e0a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -306,6 +306,7 @@ static bool need_initialization = true;
static void initialize_reloptions(void);
static void parse_one_reloption(relopt_value *option, char *text_str,
int text_len, bool validate);
+static bytea *common_am_reloptions(RegProcedure amoptions, Datum reloptions, bool validate);
/*
* initialize_reloptions
@@ -806,7 +807,8 @@ untransformRelOptions(Datum options)
* instead.
*
* tupdesc is pg_class' tuple descriptor. amoptions is the amoptions regproc
- * in the case of the tuple corresponding to an index, or InvalidOid otherwise.
+ * in the case of the tuple corresponding to an index or sequence, InvalidOid
+ * otherwise.
*/
bytea *
extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, Oid amoptions)
@@ -839,6 +841,9 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, Oid amoptions)
case RELKIND_INDEX:
options = index_reloptions(amoptions, datum, false);
break;
+ case RELKIND_SEQUENCE:
+ options = sequence_reloptions(amoptions, datum, false);
+ break;
case RELKIND_FOREIGN_TABLE:
options = NULL;
break;
@@ -1284,13 +1289,31 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
/*
* Parse options for indexes.
+ */
+bytea *
+index_reloptions(RegProcedure amoptions, Datum reloptions, bool validate)
+{
+ return common_am_reloptions(amoptions, reloptions, validate);
+}
+
+/*
+ * Parse options for sequences.
+ */
+bytea *
+sequence_reloptions(RegProcedure amoptions, Datum reloptions, bool validate)
+{
+ return common_am_reloptions(amoptions, reloptions, validate);
+}
+
+/*
+ * Parse options for indexes or sequences.
*
* amoptions Oid of option parser
* reloptions options as text[] datum
* validate error flag
*/
-bytea *
-index_reloptions(RegProcedure amoptions, Datum reloptions, bool validate)
+static bytea *
+common_am_reloptions(RegProcedure amoptions, Datum reloptions, bool validate)
{
FmgrInfo flinfo;
FunctionCallInfoData fcinfo;
diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile
new file mode 100644
index 0000000..f782f7e
--- /dev/null
+++ b/src/backend/access/sequence/Makefile
@@ -0,0 +1,17 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+# Makefile for access/sequence
+#
+# IDENTIFICATION
+# src/backend/access/sequence/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/access/sequence
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = seqam.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/sequence/seqam.c b/src/backend/access/sequence/seqam.c
new file mode 100644
index 0000000..552225a
--- /dev/null
+++ b/src/backend/access/sequence/seqam.c
@@ -0,0 +1,351 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqam.c
+ * sequence access method routines
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/sequence/seqam.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.
+ *
+ * The SQL Standard assumes that each Sequence object is completely controlled
+ * from the current database node, preventing any form of clustering mechanisms
+ * from controlling behaviour. Sequence access methods are general purpose
+ * though designed specifically to address the needs of Sequences working as
+ * part of a multi-node "cluster", though that is not defined here, nor are
+ * there dependencies on anything outside of this module, nor any particular
+ * form of clustering.
+ *
+ * The SQL Standard behaviour, also the historical PostgreSQL behaviour, is
+ * referred to as the "Local" SeqAm. That is also the basic default. Local
+ * SeqAm assumes that allocations from the sequence will be contiguous, so if
+ * user1 requests a range of values and is given 500-599 as values for their
+ * backend then the next user to make a request will be given a range starting
+ * with 600.
+ *
+ * The SeqAm mechanism allows us to override the Local behaviour, for use with
+ * clustering systems. When multiple masters can request ranges of values it
+ * would break the assumption of contiguous allocation. It seems likely that
+ * the SeqAm would also wish to control node-level caches for sequences to
+ * ensure good performance. The CACHE option and other options may be
+ * overridden by the _alloc API call, if needed, though in general having
+ * cacheing per backend and per node seems desirable.
+ *
+ * SeqAm allows calls to allocate a new range of values, reset the sequence to
+ * a new value and to define options for the AM module. The on-disk format of
+ * Sequences is the same for all AMs, except that each sequence has a SeqAm
+ * defined private-data column, am_data.
+ *
+ * We currently assume that there is no persistent state held within the SeqAm,
+ * so it is safe to ALTER the access method of an object without taking any
+ * special actions, as long as we hold an AccessExclusiveLock while we do that.
+ *
+ * SeqAMs work similarly to IndexAMs in many ways. pg_class.relam stores the
+ * Oid of the SeqAM, just as we do for IndexAm. The relcache stores AM
+ * information in much the same way for indexes and sequences, and management
+ * of options is similar also.
+ *
+ * Note that the SeqAM API calls are synchronous. It is up to the SeqAM to
+ * decide how that is handled, for example, whether there is a higher level
+ * cache at instance level to amortise network traffic in cluster.
+ *
+ * The SeqAM is identified by Oid of corresponding tuple in pg_seqam. There is
+ * no syscache for pg_seqam, though the SeqAm data is stored on the relcache
+ * entry for the sequence.
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/seqam.h"
+#include "access/reloptions.h"
+#include "access/relscan.h"
+#include "access/transam.h"
+#include "access/xact.h"
+#include "catalog/pg_seqam.h"
+#include "utils/guc.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+char *default_seqam = NULL;
+
+#define GET_SEQAM_PROCEDURE(pname, missing_ok) \
+do { \
+ procedure = &seqRelation->rd_aminfo->pname; \
+ if (!OidIsValid(procedure->fn_oid)) \
+ { \
+ RegProcedure procOid = seqRelation->rd_seqam->pname; \
+ if (RegProcedureIsValid(procOid)) \
+ fmgr_info_cxt(procOid, procedure, seqRelation->rd_indexcxt); \
+ else if (!missing_ok) \
+ elog(ERROR, "invalid %s regproc", CppAsString(pname)); \
+ } \
+} while(0)
+
+/*-------------------------------------------------------------------------
+ *
+ * Sequence Access Manager API
+ *
+ * INTERFACE ROUTINES
+ * sequence_alloc - allocate a new range of values for the sequence
+ * sequence_setval - coordinate the reset of a sequence to new value
+ * sequence_update - callback for background processing (called
+ * by sequence_request_update)
+ * sequence_seqparams - process the standard sequence parameters
+ *
+ * sequence_reloptions - process reloptions - located in reloptions.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/*
+ * sequence_alloc - allocate sequence values in a sequence
+ */
+Datum
+sequence_alloc(Relation seqRelation, int64 *current_value, int64 nrequested,
+ int64 min_value, int64 max_value, int64 increment_by,
+ bool is_cycled, Datum amdata, int64 *nallocated,
+ bool *xlog_needed)
+{
+ FmgrInfo *procedure;
+ FunctionCallInfoData fcinfo;
+
+ Assert(RelationIsValid(seqRelation));
+ Assert(PointerIsValid(seqRelation->rd_seqam));
+ Assert(OidIsValid(seqRelation->rd_rel->relam));
+
+ GET_SEQAM_PROCEDURE(seqamalloc, false);
+
+ /*
+ * have the seqam's alloc proc do it's work.
+ */
+ InitFunctionCallInfoData(fcinfo, procedure, 10, InvalidOid, NULL, NULL);
+
+ fcinfo.arg[0] = PointerGetDatum(seqRelation);
+ fcinfo.arg[1] = PointerGetDatum(current_value);
+ fcinfo.arg[2] = Int64GetDatum(nrequested);
+ fcinfo.arg[3] = Int64GetDatum(min_value);
+ fcinfo.arg[4] = Int64GetDatum(max_value);
+ fcinfo.arg[5] = Int64GetDatum(increment_by);
+ fcinfo.arg[6] = BoolGetDatum(is_cycled);
+ fcinfo.arg[7] = amdata;
+ fcinfo.arg[8] = PointerGetDatum(nallocated);
+ fcinfo.arg[9] = PointerGetDatum(xlog_needed);
+ fcinfo.argnull[0] = false;
+ fcinfo.argnull[1] = false;
+ fcinfo.argnull[2] = false;
+ fcinfo.argnull[3] = false;
+ fcinfo.argnull[4] = false;
+ fcinfo.argnull[5] = false;
+ fcinfo.argnull[6] = false;
+ fcinfo.argnull[7] = false;
+ fcinfo.argnull[8] = false;
+ fcinfo.argnull[9] = false;
+
+ return FunctionCallInvoke(&fcinfo);
+}
+
+/*
+ * sequence_setval - set sequence values in a sequence
+ */
+Datum
+sequence_setval(Relation seqRelation, int64 current_value, int64 new_value,
+ Datum amdata)
+{
+ FmgrInfo *procedure;
+ FunctionCallInfoData fcinfo;
+
+ Assert(RelationIsValid(seqRelation));
+ Assert(PointerIsValid(seqRelation->rd_seqam));
+ Assert(OidIsValid(seqRelation->rd_rel->relam));
+
+ GET_SEQAM_PROCEDURE(seqamsetval, true);
+
+ if (!OidIsValid(procedure->fn_oid))
+ return amdata;
+
+ /*
+ * have the seqam's setval proc do it's work.
+ */
+ InitFunctionCallInfoData(fcinfo, procedure, 4, InvalidOid, NULL, NULL);
+
+ fcinfo.arg[0] = PointerGetDatum(seqRelation);
+ fcinfo.arg[1] = Int64GetDatum(current_value);
+ fcinfo.arg[2] = Int64GetDatum(new_value);
+ fcinfo.arg[3] = amdata;
+ fcinfo.argnull[0] = false;
+ fcinfo.argnull[1] = false;
+ fcinfo.argnull[2] = false;
+ fcinfo.argnull[3] = false;
+
+ return FunctionCallInvoke(&fcinfo);
+}
+
+/*
+ * sequence_update - callback for sequence_request_update
+ */
+Datum
+sequence_update(Relation seqRelation, Datum amdata)
+{
+ FmgrInfo *procedure;
+ FunctionCallInfoData fcinfo;
+
+ Assert(RelationIsValid(seqRelation));
+ Assert(PointerIsValid(seqRelation->rd_seqam));
+ Assert(OidIsValid(seqRelation->rd_rel->relam));
+
+ GET_SEQAM_PROCEDURE(seqamupdate, true);
+
+ if (!OidIsValid(procedure->fn_oid))
+ return amdata;
+
+ /*
+ * have the seqam's update proc do it's work.
+ */
+ InitFunctionCallInfoData(fcinfo, procedure, 2, InvalidOid, NULL, NULL);
+
+ fcinfo.arg[0] = PointerGetDatum(seqRelation);
+ fcinfo.arg[1] = amdata;
+ fcinfo.argnull[0] = false;
+ fcinfo.argnull[1] = false;
+
+ return FunctionCallInvoke(&fcinfo);
+}
+
+/*
+ * sequence_seqparams - process standard sequence params
+ */
+Datum
+sequence_seqparams(RegProcedure amoptions, List *params, Datum amdata,
+ bool isInit)
+{
+ FmgrInfo procedure;
+ FunctionCallInfoData fcinfo;
+
+ if (!RegProcedureIsValid(amoptions))
+ return amdata;
+
+ fmgr_info(amoptions, &procedure);
+
+ /*
+ * have the seqam's seqparams proc do it's work.
+ */
+ InitFunctionCallInfoData(fcinfo, &procedure, 3, InvalidOid, NULL, NULL);
+
+ fcinfo.arg[0] = PointerGetDatum(params);
+ fcinfo.arg[1] = amdata;
+ fcinfo.arg[2] = BoolGetDatum(isInit);
+ fcinfo.argnull[0] = false;
+ fcinfo.argnull[1] = false;
+ fcinfo.argnull[2] = false;
+
+ return FunctionCallInvoke(&fcinfo);
+}
+
+/*------------------------------------------------------------
+ *
+ * Sequence Access Manager management functions
+ *
+ *------------------------------------------------------------
+ */
+
+/* check_hook: validate new default_sequenceam */
+bool
+check_default_seqam(char **newval, void **extra, GucSource source)
+{
+ if (**newval == '\0')
+ return true;
+
+ /*
+ * If we aren't inside a transaction, we cannot do database access so
+ * cannot verify the name. Must accept the value on faith.
+ */
+ if (IsTransactionState())
+ {
+ if (!OidIsValid(get_seqam_oid(*newval, true)))
+ {
+ /*
+ * When source == PGC_S_TEST, we are checking the argument of an
+ * ALTER DATABASE SET or ALTER USER SET command. Value may
+ * be created later. Because of that, issue a NOTICE if source ==
+ * PGC_S_TEST, but accept the value anyway.
+ */
+ if (source == PGC_S_TEST)
+ {
+ ereport(NOTICE,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("sequence manager \"%s\" does not exist",
+ *newval)));
+ }
+ else
+ {
+ GUC_check_errdetail("sequence manager \"%s\" does not exist.",
+ *newval);
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+/*
+ * GetDefaultSeqAM -- get the OID of the current default sequence AM
+ *
+ * This exists to hide (and possibly optimize the use of) the
+ * default_seqam GUC variable.
+ */
+Oid
+GetDefaultSeqAM(void)
+{
+ /* Fast path for default_tablespace == "" */
+ if (default_seqam == NULL || default_seqam[0] == '\0')
+ return LOCAL_SEQAM_OID;
+
+ return get_seqam_oid(default_seqam, false);
+}
+
+/*
+ * get_seqam_oid - given a sequence AM name, look up the OID
+ *
+ * If missing_ok is false, throw an error if SeqAM name not found. If true,
+ * just return InvalidOid.
+ */
+Oid
+get_seqam_oid(const char *amname, bool missing_ok)
+{
+ Oid result;
+ HeapTuple tuple;
+
+ /* look up the access method */
+ tuple = SearchSysCache1(SEQAMNAME, PointerGetDatum(amname));
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("sequence access method \"%s\" does not exist",
+ amname)));
+
+ /* We assume that there can be at most one matching tuple */
+ if (HeapTupleIsValid(tuple))
+ {
+ result = HeapTupleGetOid(tuple);
+ ReleaseSysCache(tuple);
+ }
+ else
+ result = InvalidOid;
+
+ if (!OidIsValid(result) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("squence am \"%s\" does not exist",
+ amname)));
+ return result;
+}
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index b257b02..3a6a597 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -35,7 +35,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_statistic.h pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \
pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \
pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \
- pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
+ pg_authid.h pg_auth_members.h pg_seqam.h pg_shdepend.h pg_shdescription.h \
pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
pg_ts_parser.h pg_ts_template.h pg_extension.h \
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 3b89dd0..7078f9c 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -14,13 +14,19 @@
*/
#include "postgres.h"
+#include "access/reloptions.h"
+#include "access/seqam.h"
+#include "access/transam.h"
#include "access/htup_details.h"
#include "access/multixact.h"
#include "access/transam.h"
+#include "access/xact.h"
#include "access/xlogutils.h"
#include "catalog/dependency.h"
+#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
+#include "catalog/pg_seqam.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "commands/sequence.h"
@@ -72,6 +78,7 @@ typedef struct SeqTableData
/* if last != cached, we have not used up all the cached values */
int64 increment; /* copy of sequence's increment field */
/* note that increment is zero until we first do read_seq_tuple() */
+ Datum amdata; /* private data of the SeqAm */
} SeqTableData;
typedef SeqTableData *SeqTable;
@@ -95,7 +102,14 @@ static void init_params(List *options, bool isInit,
Form_pg_sequence new, List **owned_by);
static void do_setval(Oid relid, int64 next, bool iscalled);
static void process_owned_by(Relation seqrel, List *owned_by);
-
+static void log_sequence_tuple(Relation seqrel, HeapTuple tup, Page page);
+static void replace_sequence_tuple(Relation seqrel, Buffer buf,
+ HeapTuple tuple, HeapTuple newtup);
+static void seqrel_update_relam(Oid seqoid, Oid seqamid);
+static HeapTuple seqtup_update_amdata(HeapTuple tuple, TupleDesc tupdesc, Datum amdata);
+static Datum init_seqam(Oid oldAM, Oid *newAM, char *accessMethod,
+ List *seqoptions, List *reloptions, Datum amdata,
+ bool isInit);
/*
* DefineSequence
@@ -108,6 +122,7 @@ DefineSequence(CreateSeqStmt *seq)
List *owned_by;
CreateStmt *stmt = makeNode(CreateStmt);
Oid seqoid;
+ Oid seqamid;
Relation rel;
HeapTuple tuple;
TupleDesc tupDesc;
@@ -115,6 +130,7 @@ DefineSequence(CreateSeqStmt *seq)
bool null[SEQ_COL_LASTCOL];
int i;
NameData name;
+ Datum amdata;
/* Unlogged sequences are not implemented -- not clear if useful. */
if (seq->sequence->relpersistence == RELPERSISTENCE_UNLOGGED)
@@ -140,8 +156,10 @@ DefineSequence(CreateSeqStmt *seq)
}
}
- /* Check and set all option values */
+ /* Check and set all param values */
init_params(seq->options, true, &new, &owned_by);
+ amdata = init_seqam(InvalidOid, &seqamid, seq->accessMethod,
+ seq->options, seq->amoptions, (Datum) 0, true);
/*
* Create relation (and fill value[] and null[] for the tuple)
@@ -218,6 +236,14 @@ DefineSequence(CreateSeqStmt *seq)
coldef->colname = "is_called";
value[i - 1] = BoolGetDatum(false);
break;
+ case SEQ_COL_AMDATA:
+ coldef->typeName = makeTypeNameFromOid(BYTEAOID, -1);
+ coldef->colname = "amdata";
+ if (PointerIsValid(DatumGetPointer(amdata)))
+ value[i - 1] = amdata;
+ else
+ null[i - 1] = true;
+ break;
}
stmt->tableElts = lappend(stmt->tableElts, coldef);
}
@@ -233,6 +259,14 @@ DefineSequence(CreateSeqStmt *seq)
seqoid = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId);
Assert(seqoid != InvalidOid);
+ /*
+ * After we've created the sequence's relation in pg_class, update
+ * the relam to a non-default value, if requested. We perform this
+ * as a separate update to avoid invasive changes in normal code
+ * paths and to keep the code similar between CREATE and ALTER.
+ */
+ seqrel_update_relam(seqoid, seqamid);
+
rel = heap_open(seqoid, AccessExclusiveLock);
tupDesc = RelationGetDescr(rel);
@@ -270,6 +304,8 @@ ResetSequence(Oid seq_relid)
Buffer buf;
HeapTupleData seqtuple;
HeapTuple tuple;
+ Datum amdata;
+ bool amdata_isnull;
/*
* Read the old sequence. This does a bit more work than really
@@ -279,10 +315,21 @@ ResetSequence(Oid seq_relid)
init_sequence(seq_relid, &elm, &seq_rel);
(void) read_seq_tuple(elm, seq_rel, &buf, &seqtuple);
- /*
- * Copy the existing sequence tuple.
- */
- tuple = heap_copytuple(&seqtuple);
+ seq = (Form_pg_sequence) GETSTRUCT(&seqtuple);
+
+ /* Get original amdata. */
+ amdata = fastgetattr(&seqtuple, SEQ_COL_AMDATA,
+ RelationGetDescr(seq_rel), &amdata_isnull);
+ if (amdata_isnull)
+ amdata = (Datum) 0;
+
+ /* Call into the sequence AM. */
+ amdata = sequence_setval(seq_rel, seq->last_value,
+ seq->start_value, amdata);
+
+ /* Build new tuple with updated amdata and point seq to it. */
+ tuple = seqtup_update_amdata(&seqtuple, RelationGetDescr(seq_rel), amdata);
+ seq = (Form_pg_sequence) GETSTRUCT(tuple);
/* Now we're done with the old page */
UnlockReleaseBuffer(buf);
@@ -291,7 +338,6 @@ ResetSequence(Oid seq_relid)
* Modify the copied tuple to execute the restart (compare the RESTART
* action in AlterSequence)
*/
- seq = (Form_pg_sequence) GETSTRUCT(tuple);
seq->last_value = seq->start_value;
seq->is_called = false;
seq->log_cnt = 0;
@@ -305,7 +351,9 @@ ResetSequence(Oid seq_relid)
InvalidMultiXactId);
/*
- * Insert the modified tuple into the new storage file.
+ * Insert the modified tuple into the new storage file. This will log
+ * superflously log the old values, but this isn't a performance critical
+ * path...
*/
fill_seq_with_data(seq_rel, tuple);
@@ -366,27 +414,7 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
elog(ERROR, "failed to add sequence tuple to page");
/* XLOG stuff */
- if (RelationNeedsWAL(rel))
- {
- xl_seq_rec xlrec;
- XLogRecPtr recptr;
- XLogRecData rdata[2];
-
- xlrec.node = rel->rd_node;
- rdata[0].data = (char *) &xlrec;
- rdata[0].len = sizeof(xl_seq_rec);
- rdata[0].buffer = InvalidBuffer;
- rdata[0].next = &(rdata[1]);
-
- rdata[1].data = (char *) tuple->t_data;
- rdata[1].len = tuple->t_len;
- rdata[1].buffer = InvalidBuffer;
- rdata[1].next = NULL;
-
- recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG, rdata);
-
- PageSetLSN(page, recptr);
- }
+ log_sequence_tuple(rel, tuple, page);
END_CRIT_SECTION();
@@ -402,13 +430,17 @@ Oid
AlterSequence(AlterSeqStmt *stmt)
{
Oid relid;
+ Oid seqamid;
SeqTable elm;
Relation seqrel;
Buffer buf;
HeapTupleData seqtuple;
+ HeapTuple tuple;
Form_pg_sequence seq;
FormData_pg_sequence new;
List *owned_by;
+ Datum amdata;
+ bool amdata_isnull;
/* Open and lock sequence. */
relid = RangeVarGetRelid(stmt->sequence, AccessShareLock, stmt->missing_ok);
@@ -433,45 +465,46 @@ AlterSequence(AlterSeqStmt *stmt)
/* Copy old values of options into workspace */
memcpy(&new, seq, sizeof(FormData_pg_sequence));
+ /* Get original amdata. */
+ amdata = fastgetattr(&seqtuple, SEQ_COL_AMDATA,
+ RelationGetDescr(seqrel), &amdata_isnull);
+ if (amdata_isnull)
+ amdata = (Datum) 0;
+
/* Check and set new values */
init_params(stmt->options, false, &new, &owned_by);
+ amdata = init_seqam(seqrel->rd_rel->relam, &seqamid, stmt->accessMethod,
+ stmt->options, stmt->amoptions, amdata, false);
+
+ /*
+ * Change the SeqAm, if requested, using a transactional update.
+ */
+ seqrel_update_relam(relid, seqamid);
+
+ /* Build new tuple with updated amdata and point seq to it. */
+ tuple = seqtup_update_amdata(&seqtuple, RelationGetDescr(seqrel), amdata);
+ seq = (Form_pg_sequence) GETSTRUCT(tuple);
+
+ /*
+ * Copy values.
+ * XXX: there has to be a better way...
+ */
+ seq->last_value = new.last_value;
+ seq->start_value = new.start_value;
+ seq->increment_by = new.increment_by;
+ seq->max_value = new.max_value;
+ seq->min_value = new.min_value;
+ seq->cache_value = new.cache_value;
+ seq->log_cnt = new.log_cnt;
+ seq->is_cycled = new.is_cycled;
+ seq->is_called = new.is_called;
/* Clear local cache so that we don't think we have cached numbers */
/* Note that we do not change the currval() state */
elm->cached = elm->last;
/* Now okay to update the on-disk tuple */
- START_CRIT_SECTION();
-
- memcpy(seq, &new, sizeof(FormData_pg_sequence));
-
- MarkBufferDirty(buf);
-
- /* XLOG stuff */
- if (RelationNeedsWAL(seqrel))
- {
- xl_seq_rec xlrec;
- XLogRecPtr recptr;
- XLogRecData rdata[2];
- Page page = BufferGetPage(buf);
-
- xlrec.node = seqrel->rd_node;
- rdata[0].data = (char *) &xlrec;
- rdata[0].len = sizeof(xl_seq_rec);
- rdata[0].buffer = InvalidBuffer;
- rdata[0].next = &(rdata[1]);
-
- rdata[1].data = (char *) seqtuple.t_data;
- rdata[1].len = seqtuple.t_len;
- rdata[1].buffer = InvalidBuffer;
- rdata[1].next = NULL;
-
- recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG, rdata);
-
- PageSetLSN(page, recptr);
- }
-
- END_CRIT_SECTION();
+ replace_sequence_tuple(seqrel, buf, &seqtuple, tuple);
UnlockReleaseBuffer(buf);
@@ -486,6 +519,49 @@ AlterSequence(AlterSeqStmt *stmt)
return relid;
}
+/*
+ * sequence_request_update
+ *
+ * This function should be used by SeqAMs to request update callback
+ * when they need to do background processing on the sequence.
+ */
+void
+sequence_request_update(Oid seq_relid)
+{
+ Relation seq_rel;
+ SeqTable elm;
+ Buffer buf;
+ HeapTupleData seqtuple;
+ HeapTuple tuple;
+ Datum amdata;
+ bool amdata_isnull;
+
+ /*
+ * Read the old sequence. This does a bit more work than really
+ * necessary, but it's simple, and we do want to double-check that it's
+ * indeed a sequence.
+ */
+ init_sequence(seq_relid, &elm, &seq_rel);
+ (void) read_seq_tuple(elm, seq_rel, &buf, &seqtuple);
+
+ /* Get original amdata. */
+ amdata = fastgetattr(&seqtuple, SEQ_COL_AMDATA,
+ RelationGetDescr(seq_rel), &amdata_isnull);
+ if (amdata_isnull)
+ amdata = (Datum) 0;
+
+ /* Call into the sequence AM. */
+ amdata = sequence_update(seq_rel, amdata);
+
+ /* Build new tuple with updated amdata. */
+ tuple = seqtup_update_amdata(&seqtuple, RelationGetDescr(seq_rel), amdata);
+
+ replace_sequence_tuple(seq_rel, buf, &seqtuple, tuple);
+
+ /* Now we're done with the old page */
+ UnlockReleaseBuffer(buf);
+ relation_close(seq_rel, NoLock);
+}
/*
* Note: nextval with a text argument is no longer exported as a pg_proc
@@ -522,6 +598,10 @@ nextval_oid(PG_FUNCTION_ARGS)
PG_RETURN_INT64(nextval_internal(relid));
}
+/*
+ * Sequence AM independent part of nextval() that does permission checking,
+ * returns cached values and then calls out to the SeqAM specific nextval part.
+ */
static int64
nextval_internal(Oid relid)
{
@@ -535,13 +615,15 @@ nextval_internal(Oid relid)
maxv,
minv,
cache,
- log,
fetch,
- last;
- int64 result,
- next,
- rescnt = 0;
- bool logit = false;
+ log,
+ value,
+ nallocated = 0,
+ last,
+ result;
+ bool logit = false,
+ is_cycled;
+ Datum amdata = (Datum) 0;
/* open and AccessShareLock sequence */
init_sequence(relid, &elm, &seqrel);
@@ -571,18 +653,16 @@ nextval_internal(Oid relid)
seq = read_seq_tuple(elm, seqrel, &buf, &seqtuple);
page = BufferGetPage(buf);
- last = next = result = seq->last_value;
+ value = seq->last_value;
incby = seq->increment_by;
maxv = seq->max_value;
minv = seq->min_value;
- fetch = cache = seq->cache_value;
+ is_cycled = seq->is_cycled;
+ fetch = seq->cache_value;
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
@@ -612,72 +692,39 @@ nextval_internal(Oid relid)
}
}
- while (fetch) /* try to fetch cache [+ log ] numbers */
+ /* Call into sequnce AM code. */
+ if (fetch)
{
- /*
- * 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 (!seq->is_cycled)
- {
- char buf[100];
+ bool amdata_isnull;
- snprintf(buf, sizeof(buf), INT64_FORMAT, maxv);
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("nextval: reached maximum value of sequence \"%s\" (%s)",
- RelationGetRelationName(seqrel), buf)));
- }
- 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 (!seq->is_cycled)
- {
- char buf[100];
+ /* Get original amdata. */
+ amdata = fastgetattr(&seqtuple, SEQ_COL_AMDATA,
+ RelationGetDescr(seqrel), &amdata_isnull);
+ if (amdata_isnull)
+ amdata = (Datum) 0;
- snprintf(buf, sizeof(buf), INT64_FORMAT, minv);
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("nextval: reached minimum value of sequence \"%s\" (%s)",
- RelationGetRelationName(seqrel), buf)));
- }
- 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 */
- }
+ amdata = sequence_alloc(seqrel, &value, fetch, minv, maxv, incby,
+ is_cycled, amdata, &nallocated, &logit);
+ }
+
+ cache = Min(seq->cache_value, nallocated);
+
+ if (!seq->is_called)
+ result = seq->last_value; /* return last_value if not is_called */
+ else
+ {
+ Assert(nallocated);
+ result = value;
+ cache--;
}
- log -= fetch; /* adjust for any unfetched numbers */
+ log -= cache + (fetch-nallocated);
Assert(log >= 0);
+ last = result;
+ sequence_increment(seqrel, &last, cache, minv, maxv,
+ incby, is_cycled, false);
+
/* save info in local cache */
elm->last = result; /* last returned number */
elm->cached = last; /* last fetched number */
@@ -685,53 +732,67 @@ nextval_internal(Oid relid)
last_used_seq = elm;
- /* 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))
+ if (logit)
{
- xl_seq_rec xlrec;
- XLogRecPtr recptr;
- XLogRecData rdata[2];
-
/*
* 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.
*/
+ Form_pg_sequence logseq;
+ Page temppage,
+ page;
+ HeapTuple tuple;
+ int64 next = value;
+
+ sequence_increment(seqrel, &next, nallocated, minv, maxv,
+ incby, is_cycled, false);
+
+ /* Build new tuple with updated amdata. */
+ tuple = seqtup_update_amdata(&seqtuple, RelationGetDescr(seqrel), amdata);
+ logseq = (Form_pg_sequence) GETSTRUCT(tuple);
+
+ logseq->last_value = next;
+ logseq->is_called = true;
+ logseq->log_cnt = 0;
- /* set values that will be saved in xlog */
- seq->last_value = next;
- seq->is_called = true;
- seq->log_cnt = 0;
+ /* Replace the original tuple with the updated one in-place */
+ page = BufferGetPage(buf);
- xlrec.node = seqrel->rd_node;
- rdata[0].data = (char *) &xlrec;
- rdata[0].len = sizeof(xl_seq_rec);
- rdata[0].buffer = InvalidBuffer;
- rdata[0].next = &(rdata[1]);
+ temppage = PageGetTempPageCopySpecial(page);
- rdata[1].data = (char *) seqtuple.t_data;
- rdata[1].len = seqtuple.t_len;
- rdata[1].buffer = InvalidBuffer;
- rdata[1].next = NULL;
+ if (PageAddItem(temppage, (Item) tuple->t_data, tuple->t_len,
+ FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+ elog(PANIC, "replace_sequence_tuple: failed to add item to page");
- recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG, rdata);
+ PageSetLSN(temppage, PageGetLSN(page));
- PageSetLSN(page, recptr);
+ START_CRIT_SECTION();
+
+ PageRestoreTempPage(temppage, page);
+
+ /*
+ * We must mark the buffer dirty before doing XLogInsert(); see notes in
+ * SyncOneBuffer(). However, we changed the buffer to the contents of
+ * a "future" state. 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);
+
+ seqtuple.t_len = tuple->t_len;
+
+ log_sequence_tuple(seqrel, &seqtuple, page);
+ }
+ else
+ {
+ /* the seq changes should mark the buffer dirty in any case */
+ START_CRIT_SECTION();
+ MarkBufferDirty(buf);
}
/* Now update sequence tuple to the intended final state */
@@ -748,6 +809,7 @@ nextval_internal(Oid relid)
return result;
}
+
Datum
currval_oid(PG_FUNCTION_ARGS)
{
@@ -834,7 +896,10 @@ do_setval(Oid relid, int64 next, bool iscalled)
Relation seqrel;
Buffer buf;
HeapTupleData seqtuple;
+ HeapTuple tuple;
Form_pg_sequence seq;
+ Datum amdata;
+ bool amdata_isnull;
/* open and AccessShareLock sequence */
init_sequence(relid, &elm, &seqrel);
@@ -868,6 +933,21 @@ do_setval(Oid relid, int64 next, bool iscalled)
bufm, bufx)));
}
+ /* Get original amdata. */
+ amdata = fastgetattr(&seqtuple, SEQ_COL_AMDATA,
+ RelationGetDescr(seqrel), &amdata_isnull);
+ if (amdata_isnull)
+ amdata = (Datum) 0;
+
+ /* Call into the sequence AM. */
+ amdata = sequence_setval(seqrel, seq->last_value, next, amdata);
+
+ /* Build new tuple with updated amdata and point seq to it. */
+ tuple = seqtup_update_amdata(&seqtuple, RelationGetDescr(seqrel), amdata);
+ seq = (Form_pg_sequence) GETSTRUCT(tuple);
+
+ /* common logic we don't have to duplicate in every AM implementation */
+
/* Set the currval() state only if iscalled = true */
if (iscalled)
{
@@ -878,40 +958,12 @@ do_setval(Oid relid, int64 next, bool iscalled)
/* In any case, forget any future cached numbers */
elm->cached = elm->last;
- /* 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;
- XLogRecData rdata[2];
- Page page = BufferGetPage(buf);
-
- xlrec.node = seqrel->rd_node;
- rdata[0].data = (char *) &xlrec;
- rdata[0].len = sizeof(xl_seq_rec);
- rdata[0].buffer = InvalidBuffer;
- rdata[0].next = &(rdata[1]);
-
- rdata[1].data = (char *) seqtuple.t_data;
- rdata[1].len = seqtuple.t_len;
- rdata[1].buffer = InvalidBuffer;
- rdata[1].next = NULL;
-
- recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG, rdata);
-
- PageSetLSN(page, recptr);
- }
-
- END_CRIT_SECTION();
+ /* ready to change the on-disk (or really, in-buffer) tuple */
+ replace_sequence_tuple(seqrel, buf, &seqtuple, tuple);
UnlockReleaseBuffer(buf);
@@ -1040,6 +1092,7 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
elm->lxid = InvalidLocalTransactionId;
elm->last_valid = false;
elm->last = elm->cached = elm->increment = 0;
+ elm->amdata = PointerGetDatum(NULL);
}
/*
@@ -1130,15 +1183,15 @@ read_seq_tuple(SeqTable elm, Relation rel, Buffer *buf, HeapTuple seqtuple)
}
/*
- * init_params: process the options list of CREATE or ALTER SEQUENCE,
+ * init_params: process the params list of CREATE or ALTER SEQUENCE,
* and store the values into appropriate fields of *new. Also set
- * *owned_by to any OWNED BY option, or to NIL if there is none.
+ * *owned_by to any OWNED BY param, or to NIL if there is none.
*
- * If isInit is true, fill any unspecified options with default values;
- * otherwise, do not change existing options that aren't explicitly overridden.
+ * If isInit is true, fill any unspecified params with default values;
+ * otherwise, do not change existing params that aren't explicitly overridden.
*/
static void
-init_params(List *options, bool isInit,
+init_params(List *params, bool isInit,
Form_pg_sequence new, List **owned_by)
{
DefElem *start_value = NULL;
@@ -1148,13 +1201,13 @@ init_params(List *options, bool isInit,
DefElem *min_value = NULL;
DefElem *cache_value = NULL;
DefElem *is_cycled = NULL;
- ListCell *option;
+ ListCell *param;
*owned_by = NIL;
- foreach(option, options)
+ foreach(param, params)
{
- DefElem *defel = (DefElem *) lfirst(option);
+ DefElem *defel = (DefElem *) lfirst(param);
if (strcmp(defel->defname, "increment") == 0)
{
@@ -1399,7 +1452,7 @@ init_params(List *options, bool isInit,
}
/*
- * Process an OWNED BY option for CREATE/ALTER SEQUENCE
+ * Process an OWNED BY param for CREATE/ALTER SEQUENCE
*
* Ownership permissions on the sequence are already checked,
* but if we are establishing a new owned-by dependency, we must
@@ -1415,8 +1468,7 @@ process_owned_by(Relation seqrel, List *owned_by)
nnames = list_length(owned_by);
Assert(nnames > 0);
- if (nnames == 1)
- {
+ if (nnames == 1) {
/* Must be OWNED BY NONE */
if (strcmp(strVal(linitial(owned_by)), "none") != 0)
ereport(ERROR,
@@ -1548,6 +1600,134 @@ pg_sequence_parameters(PG_FUNCTION_ARGS)
return HeapTupleGetDatum(heap_form_tuple(tupdesc, values, isnull));
}
+/*
+ * Update pg_class row for sequence to record change in relam.
+ *
+ * Call only while holding AccessExclusiveLock on sequence.
+ *
+ * Note that this is a transactional update of pg_class, rather
+ * than a non-transactional update of the tuple in the sequence's
+ * heap, as occurs elsewhere in this module.
+ */
+static void
+seqrel_update_relam(Oid seqoid, Oid seqamid)
+{
+ Relation rd;
+ HeapTuple ctup;
+ Form_pg_class pgcform;
+
+ rd = heap_open(RelationRelationId, RowExclusiveLock);
+
+ /* Fetch a copy of the tuple to scribble on */
+ ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(seqoid));
+ if (!HeapTupleIsValid(ctup))
+ elog(ERROR, "pg_class entry for sequence %u unavailable",
+ seqoid);
+ pgcform = (Form_pg_class) GETSTRUCT(ctup);
+
+ if (pgcform->relam != seqamid)
+ {
+ pgcform->relam = seqamid;
+ simple_heap_update(rd, &ctup->t_self, ctup);
+ CatalogUpdateIndexes(rd, ctup);
+ }
+
+ heap_freetuple(ctup);
+ heap_close(rd, RowExclusiveLock);
+ CommandCounterIncrement();
+}
+
+static void
+log_sequence_tuple(Relation seqrel, HeapTuple tup, Page page)
+{
+ xl_seq_rec xlrec;
+ XLogRecPtr recptr;
+ XLogRecData rdata[2];
+
+ xlrec.node = seqrel->rd_node;
+ rdata[0].data = (char *) &xlrec;
+ rdata[0].len = sizeof(xl_seq_rec);
+ rdata[0].buffer = InvalidBuffer;
+ rdata[0].next = &(rdata[1]);
+
+ rdata[1].data = (char *) tup->t_data;
+ rdata[1].len = tup->t_len;
+ rdata[1].buffer = InvalidBuffer;
+ rdata[1].next = NULL;
+
+ recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG, rdata);
+
+ PageSetLSN(page, recptr);
+}
+
+/*
+ * Replace the sequence tuple in a buffer and save it to the disk.
+ */
+static void
+replace_sequence_tuple(Relation seqrel, Buffer buf, HeapTuple tuple, HeapTuple newtup)
+{
+ Page temppage, page;
+
+ page = BufferGetPage(buf);
+
+ temppage = PageGetTempPageCopySpecial(page);
+
+ if (PageAddItem(temppage, (Item) newtup->t_data, newtup->t_len,
+ FirstOffsetNumber, false, false) == InvalidOffsetNumber)
+ elog(PANIC, "replace_sequence_tuple: failed to add item to page");
+
+ PageSetLSN(temppage, PageGetLSN(page));
+
+ START_CRIT_SECTION();
+
+ PageRestoreTempPage(temppage, page);
+
+ MarkBufferDirty(buf);
+
+ tuple->t_len = newtup->t_len;
+
+ log_sequence_tuple(seqrel, tuple, page);
+
+ END_CRIT_SECTION();
+}
+
+/*
+ * Return copy of the sequence tuple with updated amdata.
+ */
+static HeapTuple
+seqtup_update_amdata(HeapTuple tuple, TupleDesc tupdesc, Datum amdata)
+{
+ Datum values[SEQ_COL_LASTCOL];
+ bool nulls[SEQ_COL_LASTCOL];
+ bool repls[SEQ_COL_LASTCOL];
+ HeapTuple newtup;
+
+ /* Build new tuple with updated amdata */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+ memset(repls, false, sizeof(repls));
+
+ if (PointerIsValid(DatumGetPointer(amdata)))
+ values[SEQ_COL_AMDATA - 1] = amdata;
+ else
+ nulls[SEQ_COL_AMDATA - 1] = true;
+
+ repls[SEQ_COL_AMDATA - 1] = true;
+
+ newtup = heap_modify_tuple(tuple, tupdesc, values, nulls, repls);
+
+ /*
+ * Sequence tuples must be frozen, because VACUUM does not examine
+ * sequence table.
+ */
+ HeapTupleHeaderSetXmin(newtup->t_data, FrozenTransactionId);
+ HeapTupleHeaderSetXminFrozen(newtup->t_data);
+ HeapTupleHeaderSetCmin(newtup->t_data, FirstCommandId);
+ HeapTupleHeaderSetXmax(newtup->t_data, InvalidTransactionId);
+ newtup->t_data->t_infomask |= HEAP_XMAX_INVALID;
+
+ return newtup;
+}
void
seq_redo(XLogRecPtr lsn, XLogRecord *record)
@@ -1602,6 +1782,7 @@ seq_redo(XLogRecPtr lsn, XLogRecord *record)
pfree(localpage);
}
+
/*
* Flush cached sequence information.
*/
@@ -1616,3 +1797,184 @@ ResetSequenceCaches(void)
last_used_seq = NULL;
}
+
+static Datum
+init_seqam(Oid oldAM, Oid *newAM, char *accessMethod, List *seqparams,
+ List *reloptions, Datum amdata, bool isInit)
+{
+ Datum reloptions_parsed;
+ Form_pg_seqam seqamForm;
+ HeapTuple tuple = NULL;
+ char *validnsps[] = {NULL, NULL};
+ Datum res;
+
+ if (oldAM && accessMethod == NULL)
+ *newAM = oldAM;
+ else if (accessMethod == NULL || strcmp(accessMethod, DEFAULT_SEQAM) == 0)
+ *newAM = GetDefaultSeqAM();
+ else
+ *newAM = get_seqam_oid(accessMethod, false);
+
+ tuple = SearchSysCache1(SEQAMOID, ObjectIdGetDatum(*newAM));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", *newAM);
+
+ seqamForm = (Form_pg_seqam) GETSTRUCT(tuple);
+
+ /* First process/validate the standard sequence params */
+ res = sequence_seqparams(seqamForm->seqamseqparams, seqparams,
+ amdata, isInit);
+
+ /* allow am specific options */
+ validnsps[0] = NameStr(seqamForm->seqamname);
+
+ /*
+ * Parse AM-specific options, convert to text array form,
+ * retrieve the AM-option function and then validate.
+ */
+ reloptions_parsed = transformRelOptions((Datum) NULL, reloptions,
+ NULL, validnsps, false, false);
+
+ (void) sequence_reloptions(seqamForm->seqamreloptions, reloptions_parsed, true);
+
+ ReleaseSysCache(tuple);
+
+ return res;
+}
+
+
+int64
+sequence_increment(Relation seqrel, int64 *value, int64 incnum, int64 minv,
+ int64 maxv, int64 incby, bool is_cycled, bool report_errors)
+{
+ int64 next = *value;
+ int64 rescnt = 0;
+
+ while (incnum)
+ {
+ /*
+ * 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))
+ {
+ /*
+ * We were asked to not report errors, return without incrementing
+ * and let the caller handle it.
+ */
+ if (!report_errors)
+ return rescnt;
+ if (!is_cycled)
+ {
+ char buf[100];
+
+ snprintf(buf, sizeof(buf), INT64_FORMAT, maxv);
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("nextval: reached maximum value of sequence \"%s\" (%s)",
+ RelationGetRelationName(seqrel), buf)));
+ }
+ next = minv;
+ }
+ else
+ next += incby;
+ }
+ else
+ {
+ /* descending sequence */
+ if ((minv < 0 && next < minv - incby) ||
+ (minv >= 0 && next + incby < minv))
+ {
+ /*
+ * We were asked to not report errors, return without incrementing
+ * and let the caller handle it.
+ */
+ if (!report_errors)
+ return rescnt;
+ if (!is_cycled)
+ {
+ char buf[100];
+
+ snprintf(buf, sizeof(buf), INT64_FORMAT, minv);
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("nextval: reached minimum value of sequence \"%s\" (%s)",
+ RelationGetRelationName(seqrel), buf)));
+ }
+ next = maxv;
+ }
+ else
+ next += incby;
+ }
+ rescnt++;
+ incnum--;
+ }
+
+ *value = next;
+
+ return rescnt;
+}
+
+
+/*------------------------------------------------------------
+ *
+ * Sequence Access Manager = LOCAL functions
+ *
+ *------------------------------------------------------------
+ */
+
+/*
+ * sequence_local_alloc()
+ *
+ * Allocate new range of values for a local sequence.
+ */
+Datum
+sequence_local_alloc(PG_FUNCTION_ARGS)
+{
+ Relation seqrel = (Relation) PG_GETARG_POINTER(0);
+ int64 *current_value = (int64 *) PG_GETARG_POINTER(1);
+ int64 fetch = PG_GETARG_INT64(2);
+ int64 min_value = PG_GETARG_INT64(3);
+ int64 max_value = PG_GETARG_INT64(4);
+ int64 increment_by = PG_GETARG_INT64(5);
+ bool is_cycled = PG_GETARG_INT64(6);
+ int64 *nallocated = (int64 *) PG_GETARG_POINTER(8);
+ int64 next,
+ rescnt = 0;
+
+ next = *current_value;
+ rescnt = sequence_increment(seqrel, &next, 1, min_value, max_value,
+ increment_by, is_cycled, true);
+ *current_value = next;
+ fetch--;
+
+ if (fetch)
+ rescnt += sequence_increment(seqrel, &next, fetch, min_value, max_value,
+ increment_by, is_cycled, false);
+
+ *nallocated = rescnt;
+
+ PG_RETURN_NULL();
+}
+
+/*
+ * sequence_local_options()
+ *
+ * Verify the options of a local sequence.
+ */
+Datum
+sequence_local_options(PG_FUNCTION_ARGS)
+{
+ Datum reloptions = PG_GETARG_DATUM(0);
+ bool validate = PG_GETARG_BOOL(1);
+ bytea *result;
+
+ result = default_reloptions(reloptions, validate, RELOPT_KIND_SEQUENCE);
+ if (result)
+ PG_RETURN_BYTEA_P(result);
+ PG_RETURN_NULL();
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ecdff1e..90f043a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9010,6 +9010,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
case RELKIND_INDEX:
(void) index_reloptions(rel->rd_am->amoptions, newOptions, true);
break;
+ case RELKIND_SEQUENCE:
+ (void) sequence_reloptions(rel->rd_am->amoptions, newOptions, true);
+ break;
default:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 21b070a..53b7fbd 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3330,7 +3330,9 @@ _copyCreateSeqStmt(const CreateSeqStmt *from)
COPY_NODE_FIELD(sequence);
COPY_NODE_FIELD(options);
+ COPY_NODE_FIELD(amoptions);
COPY_SCALAR_FIELD(ownerId);
+ COPY_STRING_FIELD(accessMethod);
COPY_SCALAR_FIELD(if_not_exists);
return newnode;
@@ -3343,7 +3345,9 @@ _copyAlterSeqStmt(const AlterSeqStmt *from)
COPY_NODE_FIELD(sequence);
COPY_NODE_FIELD(options);
+ COPY_NODE_FIELD(amoptions);
COPY_SCALAR_FIELD(missing_ok);
+ COPY_STRING_FIELD(accessMethod);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 358395f..3c37de9 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1566,7 +1566,9 @@ _equalCreateSeqStmt(const CreateSeqStmt *a, const CreateSeqStmt *b)
{
COMPARE_NODE_FIELD(sequence);
COMPARE_NODE_FIELD(options);
+ COMPARE_NODE_FIELD(amoptions);
COMPARE_SCALAR_FIELD(ownerId);
+ COMPARE_STRING_FIELD(accessMethod);
COMPARE_SCALAR_FIELD(if_not_exists);
return true;
@@ -1577,7 +1579,9 @@ _equalAlterSeqStmt(const AlterSeqStmt *a, const AlterSeqStmt *b)
{
COMPARE_NODE_FIELD(sequence);
COMPARE_NODE_FIELD(options);
+ COMPARE_NODE_FIELD(amoptions);
COMPARE_SCALAR_FIELD(missing_ok);
+ COMPARE_STRING_FIELD(accessMethod);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c98c27a..e8d0d5e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -51,6 +51,7 @@
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/pg_seqam.h"
#include "catalog/pg_trigger.h"
#include "commands/defrem.h"
#include "commands/trigger.h"
@@ -3519,7 +3520,33 @@ CreateSeqStmt:
CreateSeqStmt *n = makeNode(CreateSeqStmt);
$4->relpersistence = $2;
n->sequence = $4;
+ n->accessMethod = DEFAULT_SEQAM;
n->options = $5;
+ n->amoptions = NIL;
+ n->ownerId = InvalidOid;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp SEQUENCE qualified_name OptSeqOptList
+ USING access_method
+ {
+ CreateSeqStmt *n = makeNode(CreateSeqStmt);
+ $4->relpersistence = $2;
+ n->sequence = $4;
+ n->accessMethod = $7;
+ n->options = $5;
+ n->amoptions = NIL;
+ n->ownerId = InvalidOid;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp SEQUENCE qualified_name OptSeqOptList
+ USING access_method WITH reloptions
+ {
+ CreateSeqStmt *n = makeNode(CreateSeqStmt);
+ $4->relpersistence = $2;
+ n->sequence = $4;
+ n->accessMethod = $7;
+ n->options = $5;
+ n->amoptions = $9;
n->ownerId = InvalidOid;
n->if_not_exists = false;
$$ = (Node *)n;
@@ -3541,7 +3568,31 @@ AlterSeqStmt:
{
AlterSeqStmt *n = makeNode(AlterSeqStmt);
n->sequence = $3;
+ n->accessMethod = NULL;
+ n->options = $4;
+ n->amoptions = NIL;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
+ | ALTER SEQUENCE qualified_name OptSeqOptList
+ USING access_method
+ {
+ AlterSeqStmt *n = makeNode(AlterSeqStmt);
+ n->sequence = $3;
+ n->accessMethod = $6;
n->options = $4;
+ n->amoptions = NIL;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
+ | ALTER SEQUENCE qualified_name OptSeqOptList
+ USING access_method WITH reloptions
+ {
+ AlterSeqStmt *n = makeNode(AlterSeqStmt);
+ n->sequence = $3;
+ n->accessMethod = $6;
+ n->options = $4;
+ n->amoptions = $8;
n->missing_ok = false;
$$ = (Node *)n;
}
@@ -3549,11 +3600,34 @@ AlterSeqStmt:
{
AlterSeqStmt *n = makeNode(AlterSeqStmt);
n->sequence = $5;
+ n->accessMethod = NULL;
n->options = $6;
+ n->amoptions = NIL;
+ n->missing_ok = true;
+ $$ = (Node *)n;
+ }
+ | ALTER SEQUENCE IF_P EXISTS qualified_name OptSeqOptList
+ USING access_method
+ {
+ AlterSeqStmt *n = makeNode(AlterSeqStmt);
+ n->sequence = $5;
+ n->accessMethod = $8;
+ n->options = $6;
+ n->amoptions = NIL;
+ n->missing_ok = true;
+ $$ = (Node *)n;
+ }
+ | ALTER SEQUENCE IF_P EXISTS qualified_name OptSeqOptList
+ USING access_method WITH reloptions
+ {
+ AlterSeqStmt *n = makeNode(AlterSeqStmt);
+ n->sequence = $5;
+ n->accessMethod = $8;
+ n->options = $6;
+ n->amoptions = $10;
n->missing_ok = true;
$$ = (Node *)n;
}
-
;
OptSeqOptList: SeqOptList { $$ = $1; }
@@ -3612,7 +3686,7 @@ SeqOptElem: CACHE NumericOnly
{
$$ = makeDefElem("restart", (Node *)$3);
}
- ;
+ ;
opt_by: BY {}
| /* empty */ {}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 7c1939f..5994d18 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -399,6 +399,7 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
seqstmt = makeNode(CreateSeqStmt);
seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
seqstmt->options = NIL;
+ seqstmt->amoptions = NIL;
/*
* If this is ALTER ADD COLUMN, make sure the sequence will be owned
diff --git a/src/backend/storage/page/bufpage.c b/src/backend/storage/page/bufpage.c
index 6351a9b..dfd3592 100644
--- a/src/backend/storage/page/bufpage.c
+++ b/src/backend/storage/page/bufpage.c
@@ -285,7 +285,10 @@ PageAddItem(Page page,
upper = (int) phdr->pd_upper - (int) alignedSize;
if (lower > upper)
+ {
+ elog(WARNING, "lower %d bigger than upper %d, offsetNumber %d", lower, upper, offsetNumber);
return InvalidOffsetNumber;
+ }
/*
* OK to insert the item. First, shuffle the existing pointers if needed.
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index eca3f97..0aa4cf2 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1059,10 +1059,12 @@ IndexScanOK(CatCache *cache, ScanKey cur_skey)
case AMOID:
case AMNAME:
+ case SEQAMOID:
+ case SEQAMNAME:
/*
- * Always do heap scans in pg_am, because it's so small there's
- * not much point in an indexscan anyway. We *must* do this when
+ * Always do heap scans in pg_am and pg_seqam, because they are
+ * too small to benefit from an indexscan. We *must* do this when
* initially building critical relcache entries, but we might as
* well just always do it.
*/
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index c813779..1799cce 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
+#include "catalog/pg_seqam.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -264,6 +265,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 RelationInitSequenceAccessInfo(Relation relation);
static void load_critical_index(Oid indexoid, Oid heapoid);
static TupleDesc GetPgClassDescriptor(void);
static TupleDesc GetPgIndexDescriptor(void);
@@ -1053,11 +1055,14 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
else
relation->rsdesc = NULL;
- /*
- * if it's an index, initialize index-related information
- */
- if (OidIsValid(relation->rd_rel->relam))
+ /* if it's an index, initialize index-related information */
+ if (relation->rd_rel->relkind == RELKIND_INDEX &&
+ OidIsValid(relation->rd_rel->relam))
RelationInitIndexAccessInfo(relation);
+ /* same for sequences */
+ else if (relation->rd_rel->relkind == RELKIND_SEQUENCE &&
+ OidIsValid(relation->rd_rel->relam))
+ RelationInitSequenceAccessInfo(relation);
/* extract reloptions if any */
RelationParseRelOptions(relation, pg_class_tuple);
@@ -1533,6 +1538,39 @@ LookupOpclassInfo(Oid operatorClassOid,
return opcentry;
}
+/*
+ * Initialize sequence-access-method support data for an index relation
+ */
+static void
+RelationInitSequenceAccessInfo(Relation rel)
+{
+ HeapTuple amtuple;
+ MemoryContext indexcxt;
+ Form_pg_seqam amform;
+
+ indexcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(rel),
+ ALLOCSET_SMALL_MINSIZE,
+ ALLOCSET_SMALL_INITSIZE,
+ ALLOCSET_SMALL_MAXSIZE);
+ rel->rd_indexcxt = indexcxt;
+
+ rel->rd_aminfo = (RelationAmInfo *)
+ MemoryContextAllocZero(rel->rd_indexcxt,
+ sizeof(RelationAmInfo));
+
+ /*
+ * Make a copy of the pg_am entry for the sequence's access method
+ */
+ amtuple = SearchSysCache1(SEQAMOID, ObjectIdGetDatum(rel->rd_rel->relam));
+ if (!HeapTupleIsValid(amtuple))
+ elog(ERROR, "cache lookup failed for access method %u",
+ rel->rd_rel->relam);
+ amform = (Form_pg_seqam) MemoryContextAlloc(rel->rd_indexcxt, sizeof(*amform));
+ memcpy(amform, GETSTRUCT(amtuple), sizeof(*amform));
+ ReleaseSysCache(amtuple);
+ rel->rd_seqam = amform;
+}
/*
* formrdesc
@@ -4785,6 +4823,22 @@ load_relcache_init_file(bool shared)
rel->rd_supportinfo = (FmgrInfo *)
MemoryContextAllocZero(indexcxt, nsupport * sizeof(FmgrInfo));
}
+ else if (rel->rd_rel->relkind == RELKIND_SEQUENCE)
+ {
+ MemoryContext indexcxt;
+ Assert(!rel->rd_isnailed);
+ Assert(false);
+
+ indexcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(rel),
+ ALLOCSET_SMALL_MINSIZE,
+ ALLOCSET_SMALL_INITSIZE,
+ ALLOCSET_SMALL_MAXSIZE);
+ rel->rd_indexcxt = indexcxt;
+ /* set up zeroed fmgr-info vectors */
+ rel->rd_aminfo = (RelationAmInfo *)
+ MemoryContextAllocZero(indexcxt, sizeof(RelationAmInfo));
+ }
else
{
/* Count nailed rels to ensure we have 'em all */
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 94d951c..66deaa6 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -54,6 +54,7 @@
#include "catalog/pg_shdepend.h"
#include "catalog/pg_shdescription.h"
#include "catalog/pg_shseclabel.h"
+#include "catalog/pg_seqam.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_ts_config.h"
@@ -631,6 +632,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {SeqAccessMethodRelationId, /* SEQAMNAME */
+ SeqAmNameIndexId,
+ 1,
+ {
+ Anum_pg_seqam_amname,
+ 0,
+ 0,
+ 0
+ },
+ 4
+ },
+ {SeqAccessMethodRelationId, /* SEQAMOID */
+ SeqAmOidIndexId,
+ 1,
+ {
+ ObjectIdAttributeNumber,
+ 0,
+ 0,
+ 0
+ },
+ 4
+ },
{StatisticRelationId, /* STATRELATTINH */
StatisticRelidAttnumInhIndexId,
3,
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 8111b93..5eba03b 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -27,6 +27,7 @@
#endif
#include "access/gin.h"
+#include "access/seqam.h"
#include "access/transam.h"
#include "access/twophase.h"
#include "access/xact.h"
@@ -2782,6 +2783,17 @@ static struct config_string ConfigureNamesString[] =
},
{
+ {"default_sequenceam", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("Sets the default sequence am for any new sequences."),
+ gettext_noop("An empty string selects the 'local' sequence am."),
+ GUC_IS_NAME | GUC_NOT_IN_SAMPLE
+ },
+ &default_seqam,
+ "",
+ check_default_seqam, NULL, NULL
+ },
+
+ {
{"temp_tablespaces", PGC_USERSET, CLIENT_CONN_STATEMENT,
gettext_noop("Sets the tablespace(s) to use for temporary tables and sort files."),
NULL,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 1a9e82e..6f896aa 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -14578,7 +14578,8 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
*incby,
*maxv = NULL,
*minv = NULL,
- *cache;
+ *cache,
+ *amname = "local";
char bufm[100],
bufx[100];
bool cycled;
@@ -14648,15 +14649,40 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
}
#endif
- startv = PQgetvalue(res, 0, 1);
- incby = PQgetvalue(res, 0, 2);
+ startv = pg_strdup(PQgetvalue(res, 0, 1));
+ incby = pg_strdup(PQgetvalue(res, 0, 2));
if (!PQgetisnull(res, 0, 3))
- maxv = PQgetvalue(res, 0, 3);
+ maxv = pg_strdup(PQgetvalue(res, 0, 3));
if (!PQgetisnull(res, 0, 4))
- minv = PQgetvalue(res, 0, 4);
- cache = PQgetvalue(res, 0, 5);
+ minv = pg_strdup(PQgetvalue(res, 0, 4));
+ cache = pg_strdup(PQgetvalue(res, 0, 5));
cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
+ PQclear(res);
+
+ res = ExecuteSqlQuery(fout, "SELECT EXISTS(SELECT 1 "
+ "FROM pg_catalog.pg_class c, "
+ "pg_catalog.pg_namespace n "
+ "WHERE n.oid = c.relnamespace "
+ "AND c.relname = 'pg_seqam' "
+ "AND c.relkind = 'r');",
+ PGRES_TUPLES_OK);
+ if (strcmp(PQgetvalue(res, 0, 0), "t") == 0)
+ {
+ PQclear(res);
+
+ printfPQExpBuffer(query, "SELECT a.seqamname\n"
+ "FROM pg_catalog.pg_seqam a, pg_catalog.pg_class c\n"
+ "WHERE c.relam = a.oid AND c.oid = %u",
+ tbinfo->dobj.catId.oid);
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ amname = pg_strdup(PQgetvalue(res, 0, 0));
+ }
+
+ PQclear(res);
+
/*
* DROP must be fully qualified in case same name appears in pg_catalog
*/
@@ -14698,6 +14724,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
" CACHE %s%s",
cache, (cycled ? "\n CYCLE" : ""));
+ appendPQExpBuffer(query, "\n USING %s", fmtId(amname));
appendPQExpBufferStr(query, ";\n");
appendPQExpBuffer(labelq, "SEQUENCE %s", fmtId(tbinfo->dobj.name));
@@ -14764,8 +14791,6 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
tbinfo->dobj.namespace->dobj.name, tbinfo->rolname,
tbinfo->dobj.catId, 0, tbinfo->dobj.dumpId);
- PQclear(res);
-
destroyPQExpBuffer(query);
destroyPQExpBuffer(delqry);
destroyPQExpBuffer(labelq);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 267f365..5df2b85 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1372,30 +1372,6 @@ describeOneTableDetails(const char *schemaname,
res = NULL;
/*
- * If it's a sequence, fetch its values and store into an array that will
- * be used later.
- */
- if (tableinfo.relkind == 'S')
- {
- printfPQExpBuffer(&buf, "SELECT * FROM %s", fmtId(schemaname));
- /* must be separate because fmtId isn't reentrant */
- appendPQExpBuffer(&buf, ".%s;", fmtId(relationname));
-
- res = PSQLexec(buf.data, false);
- if (!res)
- goto error_return;
-
- seq_values = pg_malloc((PQnfields(res) + 1) * sizeof(*seq_values));
-
- for (i = 0; i < PQnfields(res); i++)
- seq_values[i] = pg_strdup(PQgetvalue(res, 0, i));
- seq_values[i] = NULL;
-
- PQclear(res);
- res = NULL;
- }
-
- /*
* Get column info
*
* You need to modify value of "firstvcol" which will be defined below if
@@ -1439,6 +1415,8 @@ describeOneTableDetails(const char *schemaname,
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_attribute a");
appendPQExpBuffer(&buf, "\nWHERE a.attrelid = '%s' AND a.attnum > 0 AND NOT a.attisdropped", oid);
+ if (tableinfo.relkind == 'S')
+ appendPQExpBufferStr(&buf, " AND attname <> 'amdata'");
appendPQExpBufferStr(&buf, "\nORDER BY a.attnum;");
res = PSQLexec(buf.data, false);
@@ -1446,6 +1424,39 @@ describeOneTableDetails(const char *schemaname,
goto error_return;
numrows = PQntuples(res);
+ /*
+ * If it's a sequence, fetch its values and store into an array that will
+ * be used later.
+ */
+ if (tableinfo.relkind == 'S')
+ {
+ PGresult *result;
+
+ /*
+ * Use column names from the column info query, to automatically skip
+ * unwanted columns.
+ */
+ printfPQExpBuffer(&buf, "SELECT ");
+ for (i = 0; i < numrows; i++)
+ appendPQExpBuffer(&buf, i > 0 ? ", %s" : "%s", fmtId(PQgetvalue(res, i, 0)));
+ appendPQExpBuffer(&buf, " FROM %s",
+ fmtId(schemaname));
+ /* must be separate because fmtId isn't reentrant */
+ appendPQExpBuffer(&buf, ".%s;", fmtId(relationname));
+
+ result = PSQLexec(buf.data, false);
+ if (!result)
+ goto error_return;
+
+ seq_values = pg_malloc((PQnfields(result) + 1) * sizeof(*seq_values));
+
+ for (i = 0; i < PQnfields(result); i++)
+ seq_values[i] = pg_strdup(PQgetvalue(result, 0, i));
+ seq_values[i] = NULL;
+
+ PQclear(result);
+ }
+
/* Make title */
switch (tableinfo.relkind)
{
@@ -1757,6 +1768,29 @@ describeOneTableDetails(const char *schemaname,
/* Footer information about a sequence */
PGresult *result = NULL;
+ /* Get the Access Method name for the sequence */
+ printfPQExpBuffer(&buf, "SELECT a.seqamname\n"
+ "FROM pg_catalog.pg_seqam a, pg_catalog.pg_class c\n"
+ "WHERE c.relam = a.oid AND c.oid = %s", oid);
+
+ result = PSQLexec(buf.data, false);
+
+ /*
+ * If we get no rows back, don't show anything (obviously). We should
+ * never get more than one row back, but if we do, just ignore it and
+ * don't print anything.
+ */
+ if (!result)
+ goto error_return;
+ else if (PQntuples(result) == 1)
+ {
+ printfPQExpBuffer(&buf, _("Access Method: %s"),
+ PQgetvalue(result, 0, 0));
+ printTableAddFooter(&cont, buf.data);
+ }
+
+ PQclear(result);
+
/* Get the column that owns this sequence */
printfPQExpBuffer(&buf, "SELECT pg_catalog.quote_ident(nspname) || '.' ||"
"\n pg_catalog.quote_ident(relname) || '.' ||"
@@ -1774,6 +1808,8 @@ describeOneTableDetails(const char *schemaname,
oid);
result = PSQLexec(buf.data, false);
+
+ /* Same logic as above, only print result when we get one row. */
if (!result)
goto error_return;
else if (PQntuples(result) == 1)
@@ -1783,11 +1819,6 @@ describeOneTableDetails(const char *schemaname,
printTableAddFooter(&cont, buf.data);
}
- /*
- * If we get no rows back, don't show anything (obviously). We should
- * never get more than one row back, but if we do, just ignore it and
- * don't print anything.
- */
PQclear(result);
}
else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index c226448..47ae485 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -45,8 +45,10 @@ typedef enum relopt_kind
RELOPT_KIND_TABLESPACE = (1 << 7),
RELOPT_KIND_SPGIST = (1 << 8),
RELOPT_KIND_VIEW = (1 << 9),
+ RELOPT_KIND_SEQUENCE = (1 << 10),
+
/* if you add a new kind, make sure you update "last_default" too */
- RELOPT_KIND_LAST_DEFAULT = RELOPT_KIND_VIEW,
+ RELOPT_KIND_LAST_DEFAULT = RELOPT_KIND_SEQUENCE,
/* some compilers treat enums as signed ints, so we can't use 1 << 31 */
RELOPT_KIND_MAX = (1 << 30)
} relopt_kind;
@@ -271,6 +273,8 @@ extern bytea *heap_reloptions(char relkind, Datum reloptions, bool validate);
extern bytea *view_reloptions(Datum reloptions, bool validate);
extern bytea *index_reloptions(RegProcedure amoptions, Datum reloptions,
bool validate);
+extern bytea *sequence_reloptions(RegProcedure amoptions, Datum reloptions,
+ bool validate);
extern bytea *attribute_reloptions(Datum reloptions, bool validate);
extern bytea *tablespace_reloptions(Datum reloptions, bool validate);
diff --git a/src/include/access/seqam.h b/src/include/access/seqam.h
new file mode 100644
index 0000000..1664b74
--- /dev/null
+++ b/src/include/access/seqam.h
@@ -0,0 +1,51 @@
+/*-------------------------------------------------------------------------
+ *
+ * seqam.h
+ * Public header file for Sequence access method.
+ *
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/seqam.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SEQAM_H
+#define SEQAM_H
+
+#include "fmgr.h"
+
+#include "access/htup.h"
+#include "commands/sequence.h"
+#include "utils/relcache.h"
+#include "storage/buf.h"
+#include "storage/bufpage.h"
+
+extern char *default_seqam;
+
+extern Oid GetDefaultSeqAM(void);
+
+extern Datum sequence_alloc(Relation seqRelation, int64 *current_value,
+ int64 nrequested, int64 min_value,
+ int64 max_value, int64 increment_by,
+ bool is_cycled, Datum amdata,
+ int64 *nallocated, bool *xlog_needed);
+
+extern Datum sequence_setval(Relation seqRelation, int64 current_value,
+ int64 new_value, Datum amdata);
+extern Datum sequence_update(Relation seqRelation, Datum amdata);
+extern Datum sequence_seqparams(RegProcedure amoptions, List *params,
+ Datum amdata, bool isInit);
+extern void sequence_request_update(Oid seqid);
+
+extern Datum sequence_local_alloc(PG_FUNCTION_ARGS);
+extern Datum sequence_local_options(PG_FUNCTION_ARGS);
+
+extern Oid get_seqam_oid(const char *sequencename, bool missing_ok);
+
+extern int64 sequence_increment(Relation seqrel, int64 *value, int64 incnum,
+ int64 minv, int64 maxv, int64 incby,
+ bool is_cycled, bool report_errors);
+
+#endif /* SEQAM_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 870692c..e4d585f 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -206,6 +206,11 @@ DECLARE_UNIQUE_INDEX(pg_rewrite_oid_index, 2692, on pg_rewrite using btree(oid o
DECLARE_UNIQUE_INDEX(pg_rewrite_rel_rulename_index, 2693, on pg_rewrite using btree(ev_class oid_ops, rulename name_ops));
#define RewriteRelRulenameIndexId 2693
+DECLARE_UNIQUE_INDEX(pg_seqam_name_index, 6020, on pg_seqam using btree(seqamname name_ops));
+#define SeqAmNameIndexId 6020
+DECLARE_UNIQUE_INDEX(pg_seqam_oid_index, 6021, on pg_seqam using btree(oid oid_ops));
+#define SeqAmOidIndexId 6021
+
DECLARE_INDEX(pg_shdepend_depender_index, 1232, on pg_shdepend using btree(dbid oid_ops, classid oid_ops, objid oid_ops, objsubid int4_ops));
#define SharedDependDependerIndexId 1232
DECLARE_INDEX(pg_shdepend_reference_index, 1233, on pg_shdepend using btree(refclassid oid_ops, refobjid oid_ops));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 4736532..46f766f 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4987,6 +4987,11 @@ DESCR("peek at changes from replication slot");
DATA(insert OID = 3785 ( pg_logical_slot_peek_binary_changes PGNSP PGUID 12 1000 1000 25 0 f f f f f t v 4 0 2249 "19 3220 23 1009" "{19,3220,23,1009,3220,28,17}" "{i,i,i,v,o,o,o}" "{slot_name,upto_lsn,upto_nchanges,options,location,xid,data}" _null_ pg_logical_slot_peek_binary_changes _null_ _null_ _null_ ));
DESCR("peek at binary changes from replication slot");
+DATA(insert OID = 6022 ( sequence_local_alloc PGNSP PGUID 12 1 0 0 0 f f f f t f s 10 0 2281 "2281 2281 2281 2281 2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ sequence_local_alloc _null_ _null_ _null_ ));
+DESCR("Local SequenceAM allocation");
+DATA(insert OID = 6024 ( sequence_local_options PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 2281 "2281 16" _null_ _null_ _null_ _null_ sequence_local_options _null_ _null_ _null_ ));
+DESCR("Local SequenceAM options");
+
/* event triggers */
DATA(insert OID = 3566 ( pg_event_trigger_dropped_objects PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25}" "{o,o,o,o,o,o,o}" "{classid, objid, objsubid, object_type, schema_name, object_name, object_identity}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ ));
DESCR("list objects dropped by the current command");
diff --git a/src/include/catalog/pg_seqam.h b/src/include/catalog/pg_seqam.h
new file mode 100644
index 0000000..f025e76
--- /dev/null
+++ b/src/include/catalog/pg_seqam.h
@@ -0,0 +1,74 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_seqam.h
+ * definition of the system "sequence access method" relation (pg_seqam)
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_seqam.h
+ *
+ * NOTES
+ * the genbki.pl script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ * XXX do NOT break up DATA() statements into multiple lines!
+ * the scripts are not as smart as you might think...
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_SEQAM_H
+#define PG_SEQAM_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_seqam definition. cpp turns this into
+ * typedef struct FormData_pg_seqam
+ * ----------------
+ */
+#define SeqAccessMethodRelationId 32
+
+CATALOG(pg_seqam,32)
+{
+ NameData seqamname; /* access method name */
+ regproc seqamalloc; /* get next allocation of range of values function */
+ regproc seqamsetval; /* set value function */
+ regproc seqamupdate; /* update callback */
+ regproc seqamseqparams; /* process standard sequence params */
+ regproc seqamreloptions; /* parse AM-specific options */
+} FormData_pg_seqam;
+
+/* ----------------
+ * Form_pg_seqam corresponds to a pointer to a tuple with
+ * the format of pg_seqam relation.
+ * ----------------
+ */
+typedef FormData_pg_seqam *Form_pg_seqam;
+
+/* ----------------
+ * compiler constants for pg_seqam
+ * ----------------
+ */
+#define Natts_pg_seqam 6
+#define Anum_pg_seqam_amname 1
+#define Anum_pg_seqam_amalloc 2
+#define Anum_pg_seqam_amsetval 3
+#define Anum_pg_seqam_amupdate 4
+#define Anum_pg_seqam_amseqoptions 5
+#define Anum_pg_seqam_amreloptions 6
+
+/* ----------------
+ * initial contents of pg_seqam
+ * ----------------
+ */
+
+DATA(insert OID = 2 ( local sequence_local_alloc - - - sequence_local_options));
+DESCR("local sequence access method");
+#define LOCAL_SEQAM_OID 2
+
+#define DEFAULT_SEQAM ""
+
+#endif /* PG_SEQAM_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 914d155..d6d0d78 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -31,6 +31,7 @@ typedef struct FormData_pg_sequence
int64 log_cnt;
bool is_cycled;
bool is_called;
+ bytea amdata;
} FormData_pg_sequence;
typedef FormData_pg_sequence *Form_pg_sequence;
@@ -49,9 +50,10 @@ typedef FormData_pg_sequence *Form_pg_sequence;
#define SEQ_COL_LOG 8
#define SEQ_COL_CYCLE 9
#define SEQ_COL_CALLED 10
+#define SEQ_COL_AMDATA 11
#define SEQ_COL_FIRSTCOL SEQ_COL_NAME
-#define SEQ_COL_LASTCOL SEQ_COL_CALLED
+#define SEQ_COL_LASTCOL SEQ_COL_AMDATA
/* XLOG stuff */
#define XLOG_SEQ_LOG 0x00
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index cef9544..e04c05d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2011,8 +2011,10 @@ typedef struct CreateSeqStmt
{
NodeTag type;
RangeVar *sequence; /* the sequence to create */
- List *options;
+ List *options; /* standard sequence options */
+ List *amoptions; /* am specific options */
Oid ownerId; /* ID of owner, or InvalidOid for default */
+ char *accessMethod; /* USING name of access method (eg. Local) */
bool if_not_exists; /* just do nothing if it already exists? */
} CreateSeqStmt;
@@ -2020,8 +2022,10 @@ typedef struct AlterSeqStmt
{
NodeTag type;
RangeVar *sequence; /* the sequence to alter */
- List *options;
+ List *options; /* standard sequence options */
+ List *amoptions; /* am specific options */
bool missing_ok; /* skip error if a role is missing? */
+ char *accessMethod; /* USING name of access method (eg. Local) */
} AlterSeqStmt;
/* ----------------------
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 66b5cd3..68c7261 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -383,6 +383,7 @@ extern void GUC_check_errcode(int sqlerrcode);
*/
/* in commands/tablespace.c */
+extern bool check_default_seqam(char **newval, void **extra, GucSource source);
extern bool check_default_tablespace(char **newval, void **extra, GucSource source);
extern bool check_temp_tablespaces(char **newval, void **extra, GucSource source);
extern void assign_temp_tablespaces(const char *newval, void *extra);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 198b98f..9d88062 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -18,6 +18,7 @@
#include "catalog/pg_am.h"
#include "catalog/pg_class.h"
#include "catalog/pg_index.h"
+#include "catalog/pg_seqam.h"
#include "fmgr.h"
#include "nodes/bitmapset.h"
#include "rewrite/prs2lock.h"
@@ -49,10 +50,12 @@ typedef LockInfoData *LockInfo;
/*
* Cached lookup information for the frequently used index access method
- * functions, defined by the pg_am row associated with an index relation.
+ * functions, defined by the pg_am row associated with an index relation, or the pg_seqam
+ * row associated with a sequence relation.
*/
typedef struct RelationAmInfo
{
+ /* pg_am only */
FmgrInfo aminsert;
FmgrInfo ambeginscan;
FmgrInfo amgettuple;
@@ -62,6 +65,16 @@ typedef struct RelationAmInfo
FmgrInfo ammarkpos;
FmgrInfo amrestrpos;
FmgrInfo amcanreturn;
+ FmgrInfo amcostestimate;
+
+ /* pg_seqam only */
+ FmgrInfo seqamalloc;
+ FmgrInfo seqamsetval;
+ FmgrInfo seqamupdate;
+ FmgrInfo seqamseqparams;
+
+ /* Common */
+ FmgrInfo amoptions;
} RelationAmInfo;
@@ -131,23 +144,25 @@ typedef struct RelationData
struct HeapTupleData *rd_indextuple; /* all of pg_index tuple */
Form_pg_am rd_am; /* pg_am tuple for index's AM */
+ Form_pg_seqam rd_seqam; /* pg_seqam tuple for sequence's AM */
+
/*
- * index access support info (used only for an index relation)
+ * Access support info (used only for index or sequence relations)
*
* Note: only default support procs for each opclass are cached, namely
* those with lefttype and righttype equal to the opclass's opcintype. The
* arrays are indexed by support function number, which is a sufficient
* identifier given that restriction.
*
- * Note: rd_amcache is available for index AMs to cache private data about
- * an index. This must be just a cache since it may get reset at any time
+ * Note: rd_amcache is available for AMs to cache private data about
+ * an object. This must be just a cache since it may get reset at any time
* (in particular, it will get reset by a relcache inval message for the
* index). If used, it must point to a single memory chunk palloc'd in
* rd_indexcxt. A relcache reset will include freeing that chunk and
* setting rd_amcache = NULL.
*/
MemoryContext rd_indexcxt; /* private memory cxt for this stuff */
- RelationAmInfo *rd_aminfo; /* lookup info for funcs found in pg_am */
+ RelationAmInfo *rd_aminfo; /* lookup info for funcs found in pg_am or pg_seqam */
Oid *rd_opfamily; /* OIDs of op families for each index col */
Oid *rd_opcintype; /* OIDs of opclass declared input data types */
RegProcedure *rd_support; /* OIDs of support procedures */
@@ -158,7 +173,7 @@ typedef struct RelationData
Oid *rd_exclops; /* OIDs of exclusion operators, if any */
Oid *rd_exclprocs; /* OIDs of exclusion ops' procs, if any */
uint16 *rd_exclstrats; /* exclusion ops' strategy numbers, if any */
- void *rd_amcache; /* available for use by index AM */
+ void *rd_amcache; /* available for use by AM */
Oid *rd_indcollation; /* OIDs of index collations */
/*
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f97229f..2810a8d 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -78,6 +78,8 @@ enum SysCacheIdentifier
RELNAMENSP,
RELOID,
RULERELNAME,
+ SEQAMNAME,
+ SEQAMOID,
STATRELATTINH,
TABLESPACEOID,
TSCONFIGMAP,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 2c8ec11..ddd96d5 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -123,6 +123,7 @@ pg_range|t
pg_rewrite|t
pg_rowsecurity|t
pg_seclabel|t
+pg_seqam|t
pg_shdepend|t
pg_shdescription|t
pg_shseclabel|t
diff --git a/src/test/regress/expected/sequence.out b/src/test/regress/expected/sequence.out
index a27b5fd..a6d5de5 100644
--- a/src/test/regress/expected/sequence.out
+++ b/src/test/regress/expected/sequence.out
@@ -173,9 +173,9 @@ DROP SEQUENCE sequence_test;
CREATE SEQUENCE foo_seq;
ALTER TABLE foo_seq RENAME TO foo_seq_new;
SELECT * FROM foo_seq_new;
- sequence_name | last_value | start_value | increment_by | max_value | min_value | cache_value | log_cnt | is_cycled | is_called
----------------+------------+-------------+--------------+---------------------+-----------+-------------+---------+-----------+-----------
- foo_seq | 1 | 1 | 1 | 9223372036854775807 | 1 | 1 | 0 | f | f
+ sequence_name | last_value | start_value | increment_by | max_value | min_value | cache_value | log_cnt | is_cycled | is_called | amdata
+---------------+------------+-------------+--------------+---------------------+-----------+-------------+---------+-----------+-----------+--------
+ foo_seq | 1 | 1 | 1 | 9223372036854775807 | 1 | 1 | 0 | f | f |
(1 row)
SELECT nextval('foo_seq_new');
@@ -191,9 +191,9 @@ SELECT nextval('foo_seq_new');
(1 row)
SELECT * FROM foo_seq_new;
- sequence_name | last_value | start_value | increment_by | max_value | min_value | cache_value | log_cnt | is_cycled | is_called
----------------+------------+-------------+--------------+---------------------+-----------+-------------+---------+-----------+-----------
- foo_seq | 2 | 1 | 1 | 9223372036854775807 | 1 | 1 | 31 | f | t
+ sequence_name | last_value | start_value | increment_by | max_value | min_value | cache_value | log_cnt | is_cycled | is_called | amdata
+---------------+------------+-------------+--------------+---------------------+-----------+-------------+---------+-----------+-----------+--------
+ foo_seq | 2 | 1 | 1 | 9223372036854775807 | 1 | 1 | 31 | f | t |
(1 row)
DROP SEQUENCE foo_seq_new;
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 80c5706..5e98b24 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -109,6 +109,7 @@ SELECT table_name, column_name, is_updatable
ro_view19 | log_cnt | NO
ro_view19 | is_cycled | NO
ro_view19 | is_called | NO
+ ro_view19 | amdata | NO
ro_view2 | a | NO
ro_view2 | b | NO
ro_view20 | a | NO
@@ -134,7 +135,7 @@ SELECT table_name, column_name, is_updatable
rw_view16 | a | YES
rw_view16 | b | YES
rw_view16 | aa | YES
-(46 rows)
+(47 rows)
-- Read-only views
DELETE FROM ro_view1;