Hi,
I rewrote the patch with different API along the lines of what was
discussed.
The API now consists of following functions:
sequence_alloc - allocating range of new values
The function receives the sequence relation, current value, number of
requested values amdata and relevant sequence options like min/max and
returns new amdata, new current value, number of values allocated and
also if it needs wal write (that should be returned if amdata has
changed plus other reasons the AM might have to force the wal update).
sequence_setval - notification that setval is happening
This function gets sequence relation, previous value and new value plus
the amdata and returns amdata (I can imagine some complex sequence AMs
will want to throw error that setval can't be done on them).
sequence_request_update/sequence_update - used for background processing
Basically AM can call the sequence_request_update and backend will then
call the sequence_update method of an AM with current amdata and will
write the updated amdata to disk
sequence_seqparams - function to process/validate the standard sequence
options like start position, min/max, increment by etc by the AM, it's
called in addition to the standard processing
sequence_reloptions - this is the only thing that remained unchanged
from previous patch, it's meant to pass custom options to the AM
Only the alloc and reloptions methods are required (and implemented by
the local AM).
The caching, xlog writing, updating the page, etc is handled by backend,
the AM does not see the tuple at all. I decided to not pass even the
struct around and just pass the relevant options because I think if we
want to abstract the storage properly then the AM should not care about
how the pg_sequence looks like at all, even if it means that the
sequence_alloc parameter list is bit long.
For the amdata handling (which is the AM's private data variable) the
API assumes that (Datum) 0 is NULL, this seems to work well for
reloptions so should work here also and it simplifies things a little
compared to passing pointers to pointers around and making sure
everything is allocated, etc.
Sadly the fact that amdata is not fixed size and can be NULL made the
page updates of the sequence relation quite more complex that it used to
be. There are probably some optimizations possible there but I think the
patch is good enough for the review now, so I am adding it to October
commitfest.
--
Petr Jelinek http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
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;
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers