From 1a5ddbfe0b51eef2c364fe30e92d962107e87c0c Mon Sep 17 00:00:00 2001 From: Petr Jelinek Date: Thu, 31 Dec 2015 11:05:52 +0200 Subject: seqam 2015-12-31 --- doc/src/sgml/ref/alter_sequence.sgml | 55 + doc/src/sgml/ref/create_sequence.sgml | 25 + src/backend/access/Makefile | 4 +- src/backend/access/common/reloptions.c | 10 +- src/backend/access/sequence/Makefile | 17 + src/backend/access/sequence/seqam.c | 185 +++ src/backend/access/sequence/seqlocal.c | 398 +++++++ src/backend/bootstrap/bootparse.y | 1 + src/backend/catalog/heap.c | 16 +- src/backend/catalog/objectaddress.c | 3 +- src/backend/catalog/toasting.c | 1 + src/backend/commands/cluster.c | 1 + src/backend/commands/createas.c | 3 +- src/backend/commands/indexcmds.c | 12 +- src/backend/commands/opclasscmds.c | 15 +- src/backend/commands/sequence.c | 1501 +++++++++++++++---------- src/backend/commands/tablecmds.c | 46 +- src/backend/commands/typecmds.c | 3 +- src/backend/commands/view.c | 3 +- src/backend/nodes/copyfuncs.c | 4 + src/backend/nodes/equalfuncs.c | 4 + src/backend/parser/gram.y | 77 +- src/backend/parser/parse_utilcmd.c | 6 +- src/backend/tcop/utility.c | 6 +- src/backend/utils/cache/catcache.c | 4 +- src/backend/utils/cache/relcache.c | 53 +- src/backend/utils/cache/syscache.c | 8 +- src/backend/utils/misc/guc.c | 1 + src/bin/pg_dump/pg_dump.c | 115 +- src/bin/pg_dump/pg_dump.h | 1 + src/bin/psql/describe.c | 112 +- src/include/access/reloptions.h | 2 +- src/include/access/seqam.h | 109 ++ src/include/catalog/heap.h | 1 + src/include/catalog/indexing.h | 4 +- src/include/catalog/pg_am.h | 25 +- src/include/catalog/pg_proc.h | 13 + src/include/catalog/pg_type.h | 5 + src/include/commands/defrem.h | 2 +- src/include/commands/sequence.h | 31 +- src/include/commands/tablecmds.h | 2 +- src/include/nodes/nodes.h | 3 +- src/include/nodes/parsenodes.h | 8 +- src/include/utils/rel.h | 5 +- src/include/utils/syscache.h | 2 +- src/test/regress/expected/create_view.out | 5 +- src/test/regress/expected/sequence.out | 35 +- src/test/regress/expected/updatable_views.out | 95 +- src/test/regress/sql/create_view.sql | 5 +- src/test/regress/sql/sequence.sql | 10 +- 50 files changed, 2278 insertions(+), 774 deletions(-) create mode 100644 src/backend/access/sequence/Makefile create mode 100644 src/backend/access/sequence/seqam.c create mode 100644 src/backend/access/sequence/seqlocal.c create mode 100644 src/include/access/seqam.h diff --git a/doc/src/sgml/ref/alter_sequence.sgml b/doc/src/sgml/ref/alter_sequence.sgml index 47d3c82..ba2c3d9 100644 --- a/doc/src/sgml/ref/alter_sequence.sgml +++ b/doc/src/sgml/ref/alter_sequence.sgml @@ -29,9 +29,15 @@ ALTER SEQUENCE [ IF EXISTS ] name [ [ RESTART [ [ WITH ] restart ] ] [ CACHE cache ] [ [ NO ] CYCLE ] [ OWNED BY { table_name.column_name | NONE } ] + [ USING access_method [ WITH ( storage_parameter [= value] [, ... ] ) ] ] ALTER SEQUENCE [ IF EXISTS ] name OWNER TO { new_owner | CURRENT_USER | SESSION_USER } ALTER SEQUENCE [ IF EXISTS ] name RENAME TO new_name ALTER SEQUENCE [ IF EXISTS ] name SET SCHEMA new_schema +ALTER SEQUENCE [ IF EXISTS ] action [, ... ] + +where action is one of: + SET ( storage_parameter = value [, ... ] ) + RESET ( storage_parameter [, ... ] ) @@ -221,6 +227,24 @@ ALTER SEQUENCE [ IF EXISTS ] name S + USING access_method + + + The USING option specifies which sequence access + method will be used when generating the sequence numbers. + + + When the RESTART WITH restart parameter is also + given, it will be used as a starting value for the new access method. + Otherwise the nextval function will be called with the old + access method and the result will be used as start value for the new + access method. + + + + + new_owner @@ -247,6 +271,37 @@ ALTER SEQUENCE [ IF EXISTS ] name S + + SET ( storage_parameter = value [, ... ] ) + + + This clause changes one or more storage parameters for the sequence + access method. + + + + + + + RESET ( storage_parameter [, ... ] ) + + + This clause resets one or more storage parameters to their defaults. + + + + + + WITH ( storage_parameter [= value] [, ... ] ) + + + This clause specifies optional storage parameters for the new sequence + access method. + + + + + diff --git a/doc/src/sgml/ref/create_sequence.sgml b/doc/src/sgml/ref/create_sequence.sgml index 9e364ff..85840ff 100644 --- a/doc/src/sgml/ref/create_sequence.sgml +++ b/doc/src/sgml/ref/create_sequence.sgml @@ -25,6 +25,8 @@ CREATE [ TEMPORARY | TEMP ] SEQUENCE [ IF NOT EXISTS ] minvalue | NO MINVALUE ] [ MAXVALUE maxvalue | NO MAXVALUE ] [ START [ WITH ] start ] [ CACHE cache ] [ [ NO ] CYCLE ] [ OWNED BY { table_name.column_name | NONE } ] + [ USING access_method ] + [ WITH ( storage_parameter [= value] [, ... ] ) ] @@ -223,6 +225,29 @@ SELECT * FROM name; + + + USING access_method + + + The USING option specifies which sequence access + method will be used when generating the sequence numbers. The default + is "local". + + + + + + WITH ( storage_parameter [= value] [, ... ] ) + + + This clause specifies optional storage parameters for a sequence access + method. + + + + + diff --git a/src/backend/access/Makefile b/src/backend/access/Makefile index bd93a6a..223403d 100644 --- a/src/backend/access/Makefile +++ b/src/backend/access/Makefile @@ -8,7 +8,7 @@ subdir = src/backend/access top_builddir = ../../.. include $(top_builddir)/src/Makefile.global -SUBDIRS = brin common gin gist hash heap index nbtree rmgrdesc spgist \ - tablesample transam +SUBDIRS = brin common gin gist hash heap index nbtree rmgrdesc sequence \ + spgist tablesample transam include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index b52846f..d995b85 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -888,7 +888,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, amoptions_function amoptions) @@ -919,7 +920,8 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, amoptions_function amoptio options = view_reloptions(datum, false); break; case RELKIND_INDEX: - options = index_reloptions(amoptions, datum, false); + case RELKIND_SEQUENCE: + options = am_reloptions(amoptions, datum, false); break; case RELKIND_FOREIGN_TABLE: options = NULL; @@ -1372,14 +1374,14 @@ heap_reloptions(char relkind, Datum reloptions, bool validate) /* - * Parse options for indexes. + * Parse options for indexes or sequences. * * amoptions Oid of option parser * reloptions options as text[] datum * validate error flag */ bytea * -index_reloptions(amoptions_function amoptions, Datum reloptions, bool validate) +am_reloptions(amoptions_function amoptions, Datum reloptions, bool validate) { Assert(amoptions); diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile new file mode 100644 index 0000000..01a0dc8 --- /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 seqlocal.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..36c1bd8 --- /dev/null +++ b/src/backend/access/sequence/seqam.c @@ -0,0 +1,185 @@ +/*------------------------------------------------------------------------- + * + * seqam.c + * general sequence access method routines + * + * Portions Copyright (c) 1996-2015, 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 _init 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, amstate. + * + * 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_am. + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/reloptions.h" +#include "access/relscan.h" +#include "access/seqam.h" +#include "access/transam.h" +#include "access/xact.h" +#include "catalog/pg_type.h" +#include "utils/builtins.h" +#include "utils/guc.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/syscache.h" + +/* + * 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 = SearchSysCache2(AMNAMEKIND, PointerGetDatum(amname), + CharGetDatum(AMKIND_SEQUENCE)); + + /* 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("sequence access method \"%s\" does not exist", + amname))); + return result; +} + +/* + * GetSequenceAM - call the specified access method handler routine + * to get its SequenceAM struct. + */ +SequenceAM * +GetSequenceAM(Oid amhandler) +{ + Datum datum; + SequenceAM *routine; + + datum = OidFunctionCall0(amhandler); + routine = (SequenceAM *) DatumGetPointer(datum); + + if (routine == NULL || !IsA(routine, SequenceAM)) + elog(ERROR, "sequence access method handler function %u did not return an SequenceAM struct", + amhandler); + + return routine; +} + +/* + * GetSequenceAMByAMId - look up the handler of the access method + * for the given OID, and retrieve its SequenceAM struct. + */ +SequenceAM * +GetSequenceAMByAMId(Oid amoid) +{ + HeapTuple tuple; + regproc amhandler; + Form_pg_am amform; + + /* Get handler function OID for the access method */ + tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(amoid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", + amoid); + amform = (Form_pg_am) GETSTRUCT(tuple); + amhandler = amform->amhandler; + + /* Complain if handler OID is invalid */ + if (!RegProcedureIsValid(amhandler)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("sequence access method \"%s\" does not have a handler", + NameStr(amform->amname)))); + ReleaseSysCache(tuple); + + /* And finally, call the handler function. */ + return GetSequenceAM(amhandler); +} + + +SequenceAM * +GetSequenceAMForRelation(Relation relation) +{ + SequenceAM *seqam; + SequenceAM *cseqam; + + if (relation->rd_seqam == NULL) + { + if (!OidIsValid(relation->rd_rel->relam)) + { + char *relname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(relation)), + RelationGetRelationName(relation)); + elog(ERROR, "relation %s isn't sequence", relname); + } + + seqam = GetSequenceAMByAMId(relation->rd_rel->relam); + /* Save the data for later reuse in CacheMemoryContext */ + cseqam = (SequenceAM *) MemoryContextAlloc(CacheMemoryContext, + sizeof(SequenceAM)); + memcpy(cseqam, seqam, sizeof(SequenceAM)); + relation->rd_seqam = cseqam; + } + + return relation->rd_seqam; +} diff --git a/src/backend/access/sequence/seqlocal.c b/src/backend/access/sequence/seqlocal.c new file mode 100644 index 0000000..22f1740 --- /dev/null +++ b/src/backend/access/sequence/seqlocal.c @@ -0,0 +1,398 @@ +/*------------------------------------------------------------------------- + * + * seqlocal.c + * Local sequence access manager + * + * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/sequence/seqlocal.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/reloptions.h" +#include "access/seqam.h" +#include "access/xact.h" +#include "catalog/pg_type.h" +#include "commands/defrem.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "utils/builtins.h" +#include "utils/int8.h" +#include "utils/rel.h" +#include "utils/typcache.h" + +/* + * We don't want to log each fetching of a value from a sequence, + * so we pre-log a few fetches in advance. In the event of + * crash we can lose (skip over) as many values as we pre-logged. + */ +#define SEQ_LOG_VALS 32 + +static bytea *seqam_local_reloptions(Datum reloptions, bool validate); +static Datum seqam_local_init(Relation seqrel, Form_pg_sequence seq, + int64 restart_value, bool restart_requested, + bool is_init); +static int64 seqam_local_alloc(Relation seqrel, SequenceHandle *seqh, + int64 nrequested, int64 *last); +static void seqam_local_setval(Relation seqrel, SequenceHandle *seqh, + int64 new_value); +static Datum seqam_local_get_state(Relation seqrel, SequenceHandle *seqh); +static void seqam_local_set_state(Relation seqrel, SequenceHandle *seqh, + Datum amstate); + +static char * +seqam_local_state_get_part(StringInfo buf, char *ptr, bool *found) +{ + *found = false; + + resetStringInfo(buf); + + while (*ptr != ',' && *ptr != ')') + { + char ch = *ptr++; + + if (ch == '\0') + { + *found = false; + return ptr; + } + + appendStringInfoChar(buf, ch); + + if (!*found) + *found = true; + } + + return ptr; +} + +Datum +seqam_local_state_in(PG_FUNCTION_ARGS) +{ + char *state_str = PG_GETARG_CSTRING(0); + char *ptr; + Datum d; + bool found; + StringInfoData buf; + LocalSequenceState *state = palloc(sizeof(LocalSequenceState)); + + ptr = state_str; + while (*ptr && isspace((unsigned char) *ptr)) + ptr++; + + if (*ptr != '(') + goto malformed; + ptr++; + + initStringInfo(&buf); + + /* last_value is required */ + ptr = seqam_local_state_get_part(&buf, ptr, &found); + if (!found) + goto malformed; + d = DirectFunctionCall1(int8in, CStringGetDatum(buf.data)); + state->last_value = DatumGetInt64(d); + if (*ptr == ',') + ptr++; + + /* is_called is optional */ + ptr = seqam_local_state_get_part(&buf, ptr, &found); + if (found) + { + d = DirectFunctionCall1(boolin, CStringGetDatum(buf.data)); + state->is_called = DatumGetBool(d); + if (*ptr == ',') + ptr++; + } + else + state->is_called = true; + + /* log_cnt is optional */ + ptr = seqam_local_state_get_part(&buf, ptr, &found); + if (found) + { + d = DirectFunctionCall1(int4in, CStringGetDatum(buf.data)); + state->log_cnt = DatumGetInt32(d); + if (*ptr == ',') + ptr++; + } + else + state->log_cnt = 0; + + if (*ptr != ')' || *(++ptr) != '\0') + goto malformed; + + PG_RETURN_POINTER(state); + +malformed: + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed seqam_local_state literal: \"%s\"", + state_str))); +} + +Datum +seqam_local_state_out(PG_FUNCTION_ARGS) +{ + LocalSequenceState *state = (LocalSequenceState *) PG_GETARG_POINTER(0); + StringInfoData buf; + + initStringInfo(&buf); + + appendStringInfo(&buf, "("UINT64_FORMAT",%s,%d)", + state->last_value, state->is_called ? "t" : "f", + state->log_cnt); + + PG_RETURN_CSTRING(buf.data); +} + +Datum +seqam_local_handler(PG_FUNCTION_ARGS) +{ + SequenceAM *seqam = makeNode(SequenceAM); + + seqam->StateTypeOid = SEQAMLOCALSTATEOID; + seqam->ParseRelOption = seqam_local_reloptions; + seqam->Init = seqam_local_init; + seqam->Alloc = seqam_local_alloc; + seqam->Setval = seqam_local_setval; + seqam->GetState = seqam_local_get_state; + seqam->SetState = seqam_local_set_state; + + PG_RETURN_POINTER(seqam); +} + +/* + * seqam_local_reloptions() + * + * Parse and verify the reloptions of a local sequence. + */ +static bytea * +seqam_local_reloptions(Datum reloptions, bool validate) +{ + if (validate && PointerIsValid(DatumGetPointer(reloptions))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("local sequence does not accept any storage parameters"))); + + return NULL; +} + +/* + * seqam_local_init() + * + * Initialize local sequence + */ +static Datum +seqam_local_init(Relation seqrel, Form_pg_sequence seq, int64 restart_value, + bool restart_requested, bool is_init) +{ + LocalSequenceState *localseq; + + if (is_init) + localseq = palloc0(sizeof(LocalSequenceState)); + else + localseq = (LocalSequenceState *) seq->amstate; + + /* + * If this is a new sequence or RESTART was provided in ALTER we should + * reset our state to that new starting point. + */ + if (is_init || restart_requested) + { + localseq->last_value = restart_value; + localseq->is_called = false; + } + + localseq->log_cnt = 0; + + return PointerGetDatum(localseq); +} + +/* + * seqam_local_alloc() + * + * Allocate a new range of values for a local sequence. + */ +static int64 +seqam_local_alloc(Relation seqrel, SequenceHandle *seqh, int64 nrequested, + int64 *last) +{ + int64 incby, + maxv, + minv, + log, + fetch, + result, + next, + rescnt = 0; + bool is_cycled, + is_called, + logit = false; + Form_pg_sequence seq; + LocalSequenceState *localseq; + + seq = sequence_read_options(seqh); + localseq = (LocalSequenceState *) seq->amstate; + + next = result = localseq->last_value; + incby = seq->increment_by; + maxv = seq->max_value; + minv = seq->min_value; + is_cycled = seq->is_cycled; + fetch = nrequested; + log = localseq->log_cnt; + is_called = localseq->is_called; + + /* We are returning last_value if not is_called so fetch one less value. */ + if (!is_called) + { + nrequested--; + fetch--; + } + + /* + * Decide whether we should emit a WAL log record. If so, force up the + * fetch count to grab SEQ_LOG_VALS more values than we actually need to + * cache. (These will then be usable without logging.) + * + * If this is the first nextval after a checkpoint, we must force a new + * WAL record to be written anyway, else replay starting from the + * checkpoint would fail to advance the sequence past the logged values. + * In this case we may as well fetch extra values. + */ + if (log < fetch || !is_called) + { + /* Forced log to satisfy local demand for values. */ + fetch = log = fetch + SEQ_LOG_VALS; + logit = true; + } + else if (sequence_needs_wal(seqh)) + { + fetch = log = fetch + SEQ_LOG_VALS; + logit = true; + } + + /* Fetch new result value if is_called. */ + if (is_called) + { + rescnt += sequence_increment(seqrel, &next, 1, minv, maxv, incby, + is_cycled, true); + result = next; + } + + /* Fetch as many values as was requested by backend. */ + if (rescnt < nrequested) + rescnt += sequence_increment(seqrel, &next, nrequested - rescnt, minv, + maxv, incby, is_cycled, false); + + /* Last value available for calling backend. */ + *last = next; + /* Values we made available to calling backend can't be counted as cached. */ + log -= rescnt; + + /* We might need to fetch even more values for our own caching. */ + if (rescnt < fetch) + rescnt += sequence_increment(seqrel, &next, fetch - rescnt, minv, + maxv, incby, is_cycled, false); + + fetch -= rescnt; + log -= fetch; /* adjust for any unfetched numbers */ + Assert(log >= 0); + + /* + * Log our cached data. + */ + localseq->last_value = *last; + localseq->log_cnt = log; + localseq->is_called = true; + + sequence_start_update(seqh, logit); + + if (logit) + { + localseq->last_value = next; + localseq->log_cnt = 0; + localseq->is_called = true; + + sequence_save_state(seqh, PointerGetDatum(localseq), true); + } + + localseq->last_value = *last; + localseq->log_cnt = log; + localseq->is_called = true; + + sequence_save_state(seqh, PointerGetDatum(localseq), false); + + sequence_finish_update(seqh); + + return result; +} + +/* + * seqam_local_setval() + * + * Set value of a local sequence + */ +static void +seqam_local_setval(Relation seqrel, SequenceHandle *seqh, int64 new_value) +{ + Datum amstate; + LocalSequenceState *localseq; + + amstate = sequence_read_state(seqh); + localseq = (LocalSequenceState *) DatumGetPointer(amstate); + + localseq->last_value = new_value; /* last fetched number */ + localseq->is_called = true; + localseq->log_cnt = 0; /* how much is logged */ + + sequence_start_update(seqh, true); + sequence_save_state(seqh, PointerGetDatum(localseq), true); + sequence_finish_update(seqh); + + sequence_release_tuple(seqh); +} + +/* + * seqam_local_get_state() + * + * Dump state of a local sequence (for pg_dump) + */ +static Datum +seqam_local_get_state(Relation seqrel, SequenceHandle *seqh) +{ + Datum amstate; + LocalSequenceState *localseq = palloc(sizeof(LocalSequenceState)); + + amstate = sequence_read_state(seqh); + memcpy(localseq, DatumGetPointer(amstate), sizeof(LocalSequenceState)); + sequence_release_tuple(seqh); + + return PointerGetDatum(localseq); +} + +/* + * seqam_local_set_state() + * + * Restore previously dumped state of local sequence (used by pg_dump) +*/ +static void +seqam_local_set_state(Relation seqrel, SequenceHandle *seqh, + Datum amstate) +{ + (void) sequence_read_state(seqh); + + sequence_start_update(seqh, true); + sequence_save_state(seqh, amstate, true); + sequence_finish_update(seqh); + + sequence_release_tuple(seqh); +} diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y index d8d1b06..0eb92bd 100644 --- a/src/backend/bootstrap/bootparse.y +++ b/src/backend/bootstrap/bootparse.y @@ -251,6 +251,7 @@ Boot_CreateStmt: false, true, false, + InvalidOid, NULL); elog(DEBUG4, "relation created with OID %u", id); } diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 04c4f8f..d5578d9 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -87,6 +87,7 @@ static void AddNewRelationTuple(Relation pg_class_desc, Oid reloftype, Oid relowner, char relkind, + Oid relam, Datum relacl, Datum reloptions); static ObjectAddress AddNewRelationType(const char *typeName, @@ -848,6 +849,7 @@ AddNewRelationTuple(Relation pg_class_desc, Oid reloftype, Oid relowner, char relkind, + Oid relam, Datum relacl, Datum reloptions) { @@ -921,6 +923,7 @@ AddNewRelationTuple(Relation pg_class_desc, new_rel_reltup->relowner = relowner; new_rel_reltup->reltype = new_type_oid; new_rel_reltup->reloftype = reloftype; + new_rel_reltup->relam = relam; new_rel_desc->rd_att->tdtypeid = new_type_oid; @@ -1034,6 +1037,7 @@ heap_create_with_catalog(const char *relname, bool use_user_acl, bool allow_system_table_mods, bool is_internal, + Oid relam, ObjectAddress *typaddress) { Relation pg_class_desc; @@ -1262,6 +1266,7 @@ heap_create_with_catalog(const char *relname, reloftypeid, ownerid, relkind, + relam, PointerGetDatum(relacl), reloptions); @@ -1274,7 +1279,8 @@ heap_create_with_catalog(const char *relname, /* * Make a dependency link to force the relation to be deleted if its * namespace is. Also make a dependency link to its owner, as well as - * dependencies for any roles mentioned in the default ACL. + * dependencies for any roles mentioned in the default ACL. When relam + * is specified, record dependency on the * * For composite types, these dependencies are tracked for the pg_type * entry, so we needn't record them here. Likewise, TOAST tables don't @@ -1329,6 +1335,14 @@ heap_create_with_catalog(const char *relname, 0, NULL, nnewmembers, newmembers); } + + if (relkind == RELKIND_SEQUENCE) + { + referenced.classId = AccessMethodRelationId; + referenced.objectId = relam; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + } } /* Post creation hook for new relation */ diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index e44d7d0..c9ff22b 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -16,6 +16,7 @@ #include "postgres.h" #include "access/htup_details.h" +#include "access/seqam.h" #include "access/sysattr.h" #include "catalog/catalog.h" #include "catalog/indexing.h" @@ -1487,7 +1488,7 @@ get_object_address_opcf(ObjectType objtype, List *objname, bool missing_ok) ObjectAddress address; /* XXX no missing_ok support here */ - amoid = get_am_oid(strVal(linitial(objname)), false); + amoid = get_index_am_oid(strVal(linitial(objname)), false); objname = list_copy_tail(objname, 1); switch (objtype) diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c index 3652d7b..aff276b 100644 --- a/src/backend/catalog/toasting.c +++ b/src/backend/catalog/toasting.c @@ -290,6 +290,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, false, true, true, + InvalidOid, NULL); Assert(toast_relid != InvalidOid); diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 7a7fd95..241ce21 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -679,6 +679,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence, false, true, true, + InvalidOid, NULL); Assert(OIDNewHeap != InvalidOid); diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 41183f6..ba2ff14 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -385,7 +385,8 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) /* * Actually create the target table */ - intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL); + intoRelationAddr = DefineRelation(create, relkind, InvalidOid, InvalidOid, + NULL); /* * If necessary, create a TOAST table for the target table. Note that diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index b794d59..f92475f 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -154,7 +154,8 @@ CheckIndexCompatible(Oid oldId, Assert(numberOfAttributes <= INDEX_MAX_KEYS); /* look up the access method */ - tuple = SearchSysCache1(AMNAME, PointerGetDatum(accessMethodName)); + tuple = SearchSysCache2(AMNAMEKIND, PointerGetDatum(accessMethodName), + CharGetDatum(AMKIND_INDEX)); if (!HeapTupleIsValid(tuple)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), @@ -470,7 +471,8 @@ DefineIndex(Oid relationId, * look up the access method, verify it can handle the requested features */ accessMethodName = stmt->accessMethod; - tuple = SearchSysCache1(AMNAME, PointerGetDatum(accessMethodName)); + tuple = SearchSysCache2(AMNAMEKIND, PointerGetDatum(accessMethodName), + CharGetDatum(AMKIND_INDEX)); if (!HeapTupleIsValid(tuple)) { /* @@ -482,7 +484,9 @@ DefineIndex(Oid relationId, ereport(NOTICE, (errmsg("substituting access method \"gist\" for obsolete method \"rtree\""))); accessMethodName = "gist"; - tuple = SearchSysCache1(AMNAME, PointerGetDatum(accessMethodName)); + tuple = SearchSysCache2(AMNAMEKIND, + PointerGetDatum(accessMethodName), + CharGetDatum(AMKIND_INDEX)); } if (!HeapTupleIsValid(tuple)) @@ -533,7 +537,7 @@ DefineIndex(Oid relationId, reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, NULL, false, false); - (void) index_reloptions(amoptions, reloptions, true); + (void) am_reloptions(amoptions, reloptions, true); /* * Prepare arguments for index_create, primarily an IndexInfo structure. diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c index 7a50b6a..14f8e2d 100644 --- a/src/backend/commands/opclasscmds.c +++ b/src/backend/commands/opclasscmds.c @@ -353,7 +353,8 @@ DefineOpClass(CreateOpClassStmt *stmt) get_namespace_name(namespaceoid)); /* Get necessary info about access method */ - tup = SearchSysCache1(AMNAME, CStringGetDatum(stmt->amname)); + tup = SearchSysCache2(AMNAMEKIND, CStringGetDatum(stmt->amname), + CharGetDatum(AMKIND_INDEX)); if (!HeapTupleIsValid(tup)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), @@ -774,7 +775,7 @@ DefineOpFamily(CreateOpFamilyStmt *stmt) get_namespace_name(namespaceoid)); /* Get access method OID, throwing an error if it doesn't exist. */ - amoid = get_am_oid(stmt->amname, false); + amoid = get_index_am_oid(stmt->amname, false); /* XXX Should we make any privilege check against the AM? */ @@ -811,7 +812,8 @@ AlterOpFamily(AlterOpFamilyStmt *stmt) IndexAmRoutine *amroutine; /* Get necessary info about access method */ - tup = SearchSysCache1(AMNAME, CStringGetDatum(stmt->amname)); + tup = SearchSysCache2(AMNAMEKIND, CStringGetDatum(stmt->amname), + CharGetDatum(AMKIND_INDEX)); if (!HeapTupleIsValid(tup)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), @@ -1758,17 +1760,18 @@ IsThereOpFamilyInNamespace(const char *opfname, Oid opfmethod, } /* - * get_am_oid - given an access method name, look up the OID + * get_index_am_oid - given an index access method name, look up the OID * * If missing_ok is false, throw an error if access method not found. If * true, just return InvalidOid. */ Oid -get_am_oid(const char *amname, bool missing_ok) +get_index_am_oid(const char *amname, bool missing_ok) { Oid oid; - oid = GetSysCacheOid1(AMNAME, CStringGetDatum(amname)); + oid = GetSysCacheOid2(AMNAMEKIND, CStringGetDatum(amname), + CharGetDatum(AMKIND_INDEX)); if (!OidIsValid(oid) && !missing_ok) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 9c1037f..5d92918 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -14,14 +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/tupmacs.h" #include "access/xact.h" #include "access/xlog.h" #include "access/xloginsert.h" #include "access/xlogutils.h" #include "catalog/dependency.h" +#include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/objectaccess.h" #include "catalog/pg_type.h" @@ -36,19 +41,14 @@ #include "storage/smgr.h" #include "utils/acl.h" #include "utils/builtins.h" +#include "utils/int8.h" #include "utils/lsyscache.h" +#include "utils/rel.h" #include "utils/resowner.h" #include "utils/syscache.h" /* - * We don't want to log each fetching of a value from a sequence, - * so we pre-log a few fetches in advance. In the event of - * crash we can lose (skip over) as many values as we pre-logged. - */ -#define SEQ_LOG_VALS 32 - -/* * The "special area" of a sequence's buffer page looks like this. */ #define SEQ_MAGIC 0x1717 @@ -81,24 +81,112 @@ typedef SeqTableData *SeqTable; static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */ +struct SequenceHandle +{ + SeqTable elm; + Relation rel; + Buffer buf; + Oid statetyp; + int16 statetyplen; + bool statetypbyval; + HeapTupleData tup; + bool inupdate; +}; + /* * last_used_seq is updated by nextval() to point to the last used * sequence. */ static SeqTableData *last_used_seq = NULL; +static HeapTuple build_seq_tuple(Relation rel, SequenceAM *seqam, + Form_pg_sequence new, int64 restart_value); static void fill_seq_with_data(Relation rel, HeapTuple tuple); static int64 nextval_internal(Oid relid); static Relation open_share_lock(SeqTable seq); static void create_seq_hashtable(void); -static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel); -static Form_pg_sequence read_seq_tuple(SeqTable elm, Relation rel, - Buffer *buf, HeapTuple seqtuple); 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 tuple, + Buffer buf, Page page); +static HeapTuple sequence_read_tuple(SequenceHandle *seqh); + +/* + * Build template column definition for a sequence relation. +*/ +static ColumnDef * +makeSeqColumnDef(void) +{ + ColumnDef *coldef = makeNode(ColumnDef); + + coldef->inhcount = 0; + coldef->is_local = true; + coldef->is_not_null = true; + coldef->is_from_type = false; + /* Force plain storage. */ + coldef->storage = 'p'; + coldef->raw_default = NULL; + coldef->cooked_default = NULL; + coldef->collClause = NULL; + coldef->collOid = InvalidOid; + coldef->constraints = NIL; + coldef->location = -1; + + return coldef; +} + +/* + * Add additional sequence AM columns to the sequence column definition list. + */ +static List * +BuildSeqColumnDefList(Oid amstateTypeOid) +{ + List *seqcols; + int colid; + + seqcols = NIL; + for (colid = SEQ_COL_FIRSTCOL; colid <= SEQ_COL_LASTCOL; colid++) + { + ColumnDef *coldef = makeSeqColumnDef(); + + switch (colid) + { + case SEQ_COL_STARTVAL: + coldef->typeName = makeTypeNameFromOid(INT8OID, -1); + coldef->colname = "start_value"; + break; + case SEQ_COL_INCBY: + coldef->typeName = makeTypeNameFromOid(INT8OID, -1); + coldef->colname = "increment_by"; + break; + case SEQ_COL_MAXVALUE: + coldef->typeName = makeTypeNameFromOid(INT8OID, -1); + coldef->colname = "max_value"; + break; + case SEQ_COL_MINVALUE: + coldef->typeName = makeTypeNameFromOid(INT8OID, -1); + coldef->colname = "min_value"; + break; + case SEQ_COL_CACHE: + coldef->typeName = makeTypeNameFromOid(INT8OID, -1); + coldef->colname = "cache_value"; + break; + case SEQ_COL_CYCLE: + coldef->typeName = makeTypeNameFromOid(BOOLOID, -1); + coldef->colname = "is_cycled"; + break; + case SEQ_COL_AMSTATE: + coldef->typeName = makeTypeNameFromOid(amstateTypeOid, -1); + coldef->colname = "amstate"; + break; + } + + seqcols = lappend(seqcols, coldef); + } + return seqcols; +} /* * DefineSequence @@ -111,14 +199,12 @@ DefineSequence(CreateSeqStmt *seq) List *owned_by; CreateStmt *stmt = makeNode(CreateStmt); Oid seqoid; + Oid seqamid; ObjectAddress address; Relation rel; HeapTuple tuple; - TupleDesc tupDesc; - Datum value[SEQ_COL_LASTCOL]; - bool null[SEQ_COL_LASTCOL]; - int i; - NameData name; + List *seqcols; + SequenceAM *seqam; /* Unlogged sequences are not implemented -- not clear if useful. */ if (seq->sequence->relpersistence == RELPERSISTENCE_UNLOGGED) @@ -147,102 +233,33 @@ DefineSequence(CreateSeqStmt *seq) /* Check and set all option values */ init_params(seq->options, true, &new, &owned_by); - /* - * Create relation (and fill value[] and null[] for the tuple) - */ - stmt->tableElts = NIL; - for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) - { - ColumnDef *coldef = makeNode(ColumnDef); - - coldef->inhcount = 0; - coldef->is_local = true; - coldef->is_not_null = true; - coldef->is_from_type = false; - coldef->storage = 0; - coldef->raw_default = NULL; - coldef->cooked_default = NULL; - coldef->collClause = NULL; - coldef->collOid = InvalidOid; - coldef->constraints = NIL; - coldef->location = -1; - - null[i - 1] = false; - - switch (i) - { - case SEQ_COL_NAME: - coldef->typeName = makeTypeNameFromOid(NAMEOID, -1); - coldef->colname = "sequence_name"; - namestrcpy(&name, seq->sequence->relname); - value[i - 1] = NameGetDatum(&name); - break; - case SEQ_COL_LASTVAL: - coldef->typeName = makeTypeNameFromOid(INT8OID, -1); - coldef->colname = "last_value"; - value[i - 1] = Int64GetDatumFast(new.last_value); - break; - case SEQ_COL_STARTVAL: - coldef->typeName = makeTypeNameFromOid(INT8OID, -1); - coldef->colname = "start_value"; - value[i - 1] = Int64GetDatumFast(new.start_value); - break; - case SEQ_COL_INCBY: - coldef->typeName = makeTypeNameFromOid(INT8OID, -1); - coldef->colname = "increment_by"; - value[i - 1] = Int64GetDatumFast(new.increment_by); - break; - case SEQ_COL_MAXVALUE: - coldef->typeName = makeTypeNameFromOid(INT8OID, -1); - coldef->colname = "max_value"; - value[i - 1] = Int64GetDatumFast(new.max_value); - break; - case SEQ_COL_MINVALUE: - coldef->typeName = makeTypeNameFromOid(INT8OID, -1); - coldef->colname = "min_value"; - value[i - 1] = Int64GetDatumFast(new.min_value); - break; - case SEQ_COL_CACHE: - coldef->typeName = makeTypeNameFromOid(INT8OID, -1); - coldef->colname = "cache_value"; - value[i - 1] = Int64GetDatumFast(new.cache_value); - break; - case SEQ_COL_LOG: - coldef->typeName = makeTypeNameFromOid(INT8OID, -1); - coldef->colname = "log_cnt"; - value[i - 1] = Int64GetDatum((int64) 0); - break; - case SEQ_COL_CYCLE: - coldef->typeName = makeTypeNameFromOid(BOOLOID, -1); - coldef->colname = "is_cycled"; - value[i - 1] = BoolGetDatum(new.is_cycled); - break; - case SEQ_COL_CALLED: - coldef->typeName = makeTypeNameFromOid(BOOLOID, -1); - coldef->colname = "is_called"; - value[i - 1] = BoolGetDatum(false); - break; - } - stmt->tableElts = lappend(stmt->tableElts, coldef); - } + if (seq->accessMethod) + seqamid = get_seqam_oid(seq->accessMethod, false); + else + seqamid = LOCAL_SEQAM_OID; + + seqam = GetSequenceAMByAMId(seqamid); + /* Build column definitions. */ + seqcols = BuildSeqColumnDefList(seqam->StateTypeOid); stmt->relation = seq->sequence; stmt->inhRelations = NIL; stmt->constraints = NIL; - stmt->options = NIL; + stmt->options = seq->amoptions; stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; stmt->if_not_exists = seq->if_not_exists; + stmt->tableElts = seqcols; - address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL); + address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, seqamid, + NULL); seqoid = address.objectId; Assert(seqoid != InvalidOid); rel = heap_open(seqoid, AccessExclusiveLock); - tupDesc = RelationGetDescr(rel); - /* now initialize the sequence's data */ - tuple = heap_form_tuple(tupDesc, value, null); + /* Build new sequence tuple and store it. */ + tuple = build_seq_tuple(rel, seqam, &new, new.start_value); fill_seq_with_data(rel, tuple); /* process OWNED BY if given */ @@ -254,6 +271,7 @@ DefineSequence(CreateSeqStmt *seq) return address; } + /* * Reset a sequence to its initial value. * @@ -267,58 +285,89 @@ DefineSequence(CreateSeqStmt *seq) * responsible for permissions checking. */ void -ResetSequence(Oid seq_relid) +ResetSequence(Oid seqrelid) { - Relation seq_rel; - SeqTable elm; - Form_pg_sequence seq; - Buffer buf; - HeapTupleData seqtuple; HeapTuple tuple; + Relation seqrel; + SequenceHandle seqh; + Form_pg_sequence seq; + TupleDesc tupDesc; + Datum values[SEQ_COL_LASTCOL]; + bool nulls[SEQ_COL_LASTCOL]; + SequenceAM *seqam; /* - * 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. + * Read and lock the old page. */ - init_sequence(seq_relid, &elm, &seq_rel); - (void) read_seq_tuple(elm, seq_rel, &buf, &seqtuple); + sequence_open(seqrelid, &seqh); + tuple = sequence_read_tuple(&seqh); + seqrel = seqh.rel; + seqam = GetSequenceAMForRelation(seqrel); /* * Copy the existing sequence tuple. */ - tuple = heap_copytuple(&seqtuple); + tuple = heap_copytuple(tuple); /* Now we're done with the old page */ - UnlockReleaseBuffer(buf); + sequence_release_tuple(&seqh); - /* - * 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; + tupDesc = RelationGetDescr(seqrel); + heap_deform_tuple(tuple, tupDesc, values, nulls); + values[SEQ_COL_AMSTATE - 1] = seqam->Init(seqrel, seq, seq->start_value, + true, false); + tuple = heap_form_tuple(tupDesc, values, nulls); /* * Create a new storage file for the sequence. We want to keep the * sequence's relfrozenxid at 0, since it won't contain any unfrozen XIDs. * Same with relminmxid, since a sequence will never contain multixacts. */ - RelationSetNewRelfilenode(seq_rel, seq_rel->rd_rel->relpersistence, + RelationSetNewRelfilenode(seqrel, seqh.rel->rd_rel->relpersistence, InvalidTransactionId, InvalidMultiXactId); /* * Insert the modified tuple into the new storage file. */ - fill_seq_with_data(seq_rel, tuple); + fill_seq_with_data(seqrel, tuple); /* 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; + seqh.elm->cached = seqh.elm->last; - relation_close(seq_rel, NoLock); + /* And we're done, close the sequence. */ + sequence_close(&seqh); +} + +/* + * Build sequence tuple based on the sequence form and fill in the + * sequence AM specific info as well. + */ +static HeapTuple +build_seq_tuple(Relation rel, SequenceAM *seqam, Form_pg_sequence new, + int64 restart_value) +{ + TupleDesc tupDesc; + HeapTuple tuple; + Datum values[SEQ_COL_LASTCOL]; + bool nulls[SEQ_COL_LASTCOL]; + + tupDesc = RelationGetDescr(rel); + + memset(nulls, 0, sizeof(nulls)); + + values[SEQ_COL_STARTVAL - 1] = Int64GetDatumFast(new->start_value); + values[SEQ_COL_INCBY - 1] = Int64GetDatumFast(new->increment_by); + values[SEQ_COL_MAXVALUE - 1] = Int64GetDatumFast(new->max_value); + values[SEQ_COL_MINVALUE - 1] = Int64GetDatumFast(new->min_value); + values[SEQ_COL_CACHE - 1] = Int64GetDatumFast(new->cache_value); + values[SEQ_COL_CYCLE - 1] = BoolGetDatum(new->is_cycled); + values[SEQ_COL_AMSTATE - 1] = seqam->Init(rel, new, restart_value, + false, true); + tuple = heap_form_tuple(tupDesc, values, nulls); + + return tuple; } /* @@ -361,7 +410,13 @@ fill_seq_with_data(Relation rel, HeapTuple tuple) tuple->t_data->t_infomask |= HEAP_XMAX_INVALID; ItemPointerSet(&tuple->t_data->t_ctid, 0, FirstOffsetNumber); - /* check the comment above nextval_internal()'s equivalent call. */ + /* + * If something needs to be WAL logged, make sure that xid was acquired, + * so this transaction's commit will trigger a WAL flush and wait for + * syncrep. It's sufficient to ensure the toplevel transaction has a xid, + * no need to assign xids subxacts, that'll already trigger a appropriate + * wait. (Has to be done outside of critical section). + */ if (RelationNeedsWAL(rel)) GetTopTransactionId(); @@ -375,23 +430,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; - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.node = rel->rd_node; - - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) tuple->t_data, tuple->t_len); - - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } + log_sequence_tuple(rel, tuple, buf, page); END_CRIT_SECTION(); @@ -399,6 +438,69 @@ fill_seq_with_data(Relation rel, HeapTuple tuple) } /* + * Replace the type of amstate column. + * + * We don't do AlterTable here as that produces dead columns which we don't + * want. This is safe because the sequence page is controlled by code in this + * module and isn't changed the same way as a table. + * + * TODO: check if anybody is depending on the type associated with the + * sequence. + */ +static void +replace_sequence_amstate_col(Oid seqrelid, Oid typid) +{ + Relation attr_rel; + Datum values[Natts_pg_attribute]; + bool nulls[Natts_pg_attribute]; + bool replace[Natts_pg_attribute]; + HeapTuple tp, + attr_tuple, + newattr_tuple; + Form_pg_type typtup; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for type %u", typid); + + typtup = (Form_pg_type) GETSTRUCT(tp); + + memset(nulls, 0, sizeof(nulls)); + memset(replace, 0, sizeof(replace)); + + replace[Anum_pg_attribute_atttypid - 1] = true; + replace[Anum_pg_attribute_attlen - 1] = true; + replace[Anum_pg_attribute_attbyval - 1] = true; + replace[Anum_pg_attribute_attalign - 1] = true; + + values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(typid); + values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(typtup->typlen); + values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(typtup->typbyval); + values[Anum_pg_attribute_attalign - 1] = CharGetDatum(typtup->typalign); + + /* Build DROP command for amstate of old AM. */ + attr_rel = heap_open(AttributeRelationId, RowExclusiveLock); + + attr_tuple = SearchSysCache2(ATTNUM, + ObjectIdGetDatum(seqrelid), + Int16GetDatum(SEQ_COL_AMSTATE)); + if (!HeapTupleIsValid(attr_tuple)) /* shouldn't happen */ + elog(ERROR, "cache lookup failed for attribute %d of relation %u", + SEQ_COL_AMSTATE, seqrelid); + + newattr_tuple = heap_modify_tuple(attr_tuple, RelationGetDescr(attr_rel), + values, nulls, replace); + simple_heap_update(attr_rel, &newattr_tuple->t_self, newattr_tuple); + CatalogUpdateIndexes(attr_rel, newattr_tuple); + + ReleaseSysCache(tp); + heap_freetuple(newattr_tuple); + ReleaseSysCache(attr_tuple); + + heap_close(attr_rel, RowExclusiveLock); +} + +/* * AlterSequence * * Modify the definition of a sequence relation @@ -406,19 +508,24 @@ fill_seq_with_data(Relation rel, HeapTuple tuple) ObjectAddress AlterSequence(AlterSeqStmt *stmt) { - Oid relid; - SeqTable elm; + Oid seqrelid; + Oid oldamid; + Oid seqamid; + HeapTuple tuple; Relation seqrel; - Buffer buf; - HeapTupleData seqtuple; - Form_pg_sequence seq; - FormData_pg_sequence new; + Form_pg_sequence seq, + new; List *owned_by; ObjectAddress address; + int64 restart_value; + bool restart_requested; + SequenceHandle seqh; + SequenceAM *oldseqam; /* Open and lock sequence. */ - relid = RangeVarGetRelid(stmt->sequence, AccessShareLock, stmt->missing_ok); - if (relid == InvalidOid) + seqrelid = RangeVarGetRelid(stmt->sequence, AccessExclusiveLock, stmt->missing_ok); + + if (seqrelid == InvalidOid) { ereport(NOTICE, (errmsg("relation \"%s\" does not exist, skipping", @@ -426,70 +533,187 @@ AlterSequence(AlterSeqStmt *stmt) return InvalidObjectAddress; } - init_sequence(relid, &elm, &seqrel); + sequence_open(seqrelid, &seqh); + seqrel = seqh.rel; + oldamid = seqrel->rd_rel->relam; + oldseqam = GetSequenceAMByAMId(oldamid); /* allow ALTER to sequence owner only */ - if (!pg_class_ownercheck(relid, GetUserId())) + if (!pg_class_ownercheck(seqrelid, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, stmt->sequence->relname); /* lock page' buffer and read tuple into new sequence structure */ - seq = read_seq_tuple(elm, seqrel, &buf, &seqtuple); + tuple = sequence_read_tuple(&seqh); + seq = (Form_pg_sequence) GETSTRUCT(tuple); /* Copy old values of options into workspace */ - memcpy(&new, seq, sizeof(FormData_pg_sequence)); + tuple = heap_copytuple(tuple); + new = (Form_pg_sequence) GETSTRUCT(tuple); /* Check and set new values */ - init_params(stmt->options, false, &new, &owned_by); + init_params(stmt->options, false, new, &owned_by); - /* Clear local cache so that we don't think we have cached numbers */ - /* Note that we do not change the currval() state */ - elm->cached = elm->last; + if (stmt->accessMethod) + seqamid = get_seqam_oid(stmt->accessMethod, false); + else + seqamid = oldamid; - /* check the comment above nextval_internal()'s equivalent call. */ - if (RelationNeedsWAL(seqrel)) - GetTopTransactionId(); + restart_value = sequence_get_restart_value(stmt->options, new->start_value, + &restart_requested); - /* Now okay to update the on-disk tuple */ - START_CRIT_SECTION(); + /* + * If we are changing sequence AM, we need to alter the sequence relation. + */ + if (seqamid == oldamid) + { + ObjectAddress myself, + referenced; + Relation pgcrel; + HeapTuple pgctup, + newpgctuple; + HeapTuple seqamtup; + Form_pg_am form_am; + Datum reloptions; + Datum values[Natts_pg_class]; + bool nulls[Natts_pg_class]; + bool replace[Natts_pg_class]; + static char *validnsps[2]; + SequenceAM *newseqam; + + oldseqam = GetSequenceAMByAMId(oldamid); - memcpy(seq, &new, sizeof(FormData_pg_sequence)); + /* + * If RESTART [WITH] option was not specified in ALTER SEQUENCE + * statement, we use nextval of the old sequence AM to provide + * restart point for the new sequence AM. + */ + if (!restart_requested) + { + int64 last; + restart_value = oldseqam->Alloc(seqrel, &seqh, 1, &last); + } - MarkBufferDirty(buf); + sequence_check_range(restart_value, new->min_value, new->max_value, "RESTART"); - /* XLOG stuff */ - if (RelationNeedsWAL(seqrel)) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - Page page = BufferGetPage(buf); + /* We don't need the old sequence tuple anymore. */ + sequence_release_tuple(&seqh); + + /* Parse the new reloptions. */ + seqamtup = SearchSysCache1(AMOID, ObjectIdGetDatum(seqamid)); + if (!HeapTupleIsValid(seqamtup)) + elog(ERROR, "cache lookup failed for sequence access method %u", + seqamid); + + newseqam = GetSequenceAMByAMId(seqamid); + + form_am = (Form_pg_am) GETSTRUCT(seqamtup); - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + validnsps[0] = NameStr(form_am->amname); + validnsps[1] = NULL; - xlrec.node = seqrel->rd_node; - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); + reloptions = transformRelOptions((Datum) 0, stmt->amoptions, NULL, + validnsps, true, false); - XLogRegisterData((char *) seqtuple.t_data, seqtuple.t_len); + (void) am_reloptions(newseqam->ParseRelOption, reloptions, true); + ReleaseSysCache(seqamtup); - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); + /* Update the pg_class entry. */ + pgcrel = heap_open(RelationRelationId, RowExclusiveLock); + pgctup = SearchSysCache1(RELOID, ObjectIdGetDatum(seqrelid)); + if (!HeapTupleIsValid(pgctup)) + elog(ERROR, "pg_class entry for sequence %u unavailable", + seqrelid); - PageSetLSN(page, recptr); + memset(values, 0, sizeof(values)); + memset(nulls, false, sizeof(nulls)); + memset(replace, false, sizeof(replace)); + + values[Anum_pg_class_relam - 1] = ObjectIdGetDatum(seqamid); + replace[Anum_pg_class_relam - 1] = true; + + if (reloptions != (Datum) 0) + values[Anum_pg_class_reloptions - 1] = reloptions; + else + nulls[Anum_pg_class_reloptions - 1] = true; + replace[Anum_pg_class_reloptions - 1] = true; + + newpgctuple = heap_modify_tuple(pgctup, RelationGetDescr(pgcrel), + values, nulls, replace); + + simple_heap_update(pgcrel, &newpgctuple->t_self, newpgctuple); + + CatalogUpdateIndexes(pgcrel, newpgctuple); + + heap_freetuple(newpgctuple); + ReleaseSysCache(pgctup); + + heap_close(pgcrel, NoLock); + + CommandCounterIncrement(); + + /* + * Create a new storage file for the sequence. + * And change the type definition. + * + * We can't use AlterTable internals here because the sequence + * has to have the expected number of columns and no + * attisdropped = true columns. + */ + RelationSetNewRelfilenode(seqrel, seqrel->rd_rel->relpersistence, + InvalidTransactionId, InvalidMultiXactId); + replace_sequence_amstate_col(seqrelid, newseqam->StateTypeOid); + CommandCounterIncrement(); + + /* Rebuild the sequence tuple and save it. */ + tuple = build_seq_tuple(seqrel, newseqam, new, restart_value); + fill_seq_with_data(seqh.rel, tuple); + + /* Remove dependency on previous SeqAM */ + deleteDependencyRecordsForClass(RelationRelationId, seqrelid, + AccessMethodRelationId, + DEPENDENCY_NORMAL); + + /* Record dependency on new SeqAM */ + myself.classId = RelationRelationId; + myself.objectId = seqrelid; + myself.objectSubId = 0; + referenced.classId = AccessMethodRelationId; + referenced.objectId = seqamid; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } + else + { + Datum newamstate; - END_CRIT_SECTION(); + sequence_check_range(restart_value, new->min_value, new->max_value, + restart_requested ? "RESTART" : "START"); - UnlockReleaseBuffer(buf); + /* Let the new sequence AM initialize. */ + newamstate = oldseqam->Init(seqrel, new, restart_value, + restart_requested, false); + + sequence_start_update(&seqh, true); + memcpy(seq, new, offsetof(FormData_pg_sequence, amstate)); + sequence_save_state(&seqh, newamstate, true); + sequence_finish_update(&seqh); + sequence_release_tuple(&seqh); + } + + /* Clear local cache so that we don't think we have cached numbers */ + /* Note that we do not change the currval() state */ + seqh.elm->cached = seqh.elm->last; /* process OWNED BY if given */ if (owned_by) process_owned_by(seqrel, owned_by); - InvokeObjectPostAlterHook(RelationRelationId, relid, 0); + InvokeObjectPostAlterHook(RelationRelationId, seqrelid, 0); - ObjectAddressSet(address, RelationRelationId, relid); + ObjectAddressSet(address, RelationRelationId, seqrelid); - relation_close(seqrel, NoLock); + sequence_close(&seqh); return address; } @@ -530,29 +754,26 @@ 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) { SeqTable elm; Relation seqrel; - Buffer buf; - Page page; - HeapTupleData seqtuple; - Form_pg_sequence seq; - int64 incby, - maxv, - minv, - cache, - log, - fetch, - last; - int64 result, - next, - rescnt = 0; - bool logit = false; + Form_pg_sequence seq_form; + int64 last, + result; + SequenceHandle seqh; + SequenceAM *seqam; /* open and AccessShareLock sequence */ - init_sequence(relid, &elm, &seqrel); + sequence_open(relid, &seqh); + elm = seqh.elm; + seqrel = seqh.rel; + seqam = GetSequenceAMForRelation(seqrel); if (pg_class_aclcheck(elm->relid, GetUserId(), ACL_USAGE | ACL_UPDATE) != ACLCHECK_OK) @@ -577,121 +798,15 @@ nextval_internal(Oid relid) Assert(elm->last_valid); Assert(elm->increment != 0); elm->last += elm->increment; - relation_close(seqrel, NoLock); + sequence_close(&seqh); last_used_seq = elm; return elm->last; } /* lock page' buffer and read tuple */ - seq = read_seq_tuple(elm, seqrel, &buf, &seqtuple); - page = BufferGetPage(buf); - - last = next = result = seq->last_value; - incby = seq->increment_by; - maxv = seq->max_value; - minv = seq->min_value; - fetch = cache = 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 - * fetch count to grab SEQ_LOG_VALS more values than we actually need to - * cache. (These will then be usable without logging.) - * - * If this is the first nextval after a checkpoint, we must force a new - * WAL record to be written anyway, else replay starting from the - * checkpoint would fail to advance the sequence past the logged values. - * In this case we may as well fetch extra values. - */ - if (log < fetch || !seq->is_called) - { - /* forced log to satisfy local demand for values */ - fetch = log = fetch + SEQ_LOG_VALS; - logit = true; - } - else - { - XLogRecPtr redoptr = GetRedoRecPtr(); - - if (PageGetLSN(page) <= redoptr) - { - /* last update of seq was before checkpoint */ - fetch = log = fetch + SEQ_LOG_VALS; - logit = true; - } - } - - while (fetch) /* try to fetch cache [+ log ] numbers */ - { - /* - * Check MAXVALUE for ascending sequences and MINVALUE for descending - * sequences - */ - if (incby > 0) - { - /* ascending sequence */ - if ((maxv >= 0 && next > maxv - incby) || - (maxv < 0 && next + incby > maxv)) - { - if (rescnt > 0) - break; /* stop fetching */ - if (!seq->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)) - { - if (rescnt > 0) - break; /* stop fetching */ - if (!seq->is_cycled) - { - char buf[100]; + seq_form = (Form_pg_sequence) GETSTRUCT(sequence_read_tuple(&seqh)); - 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 */ - } - } - - log -= fetch; /* adjust for any unfetched numbers */ - Assert(log >= 0); + result = seqam->Alloc(seqrel, &seqh, seq_form->cache_value, &last); /* save info in local cache */ elm->last = result; /* last returned number */ @@ -700,70 +815,8 @@ nextval_internal(Oid relid) last_used_seq = elm; - /* - * If something needs to be WAL logged, acquire an xid, so this - * transaction's commit will trigger a WAL flush and wait for syncrep. - * It's sufficient to ensure the toplevel transaction has an xid, no need - * to assign xids subxacts, that'll already trigger an appropriate wait. - * (Have to do that here, so we're outside the critical section) - */ - if (logit && RelationNeedsWAL(seqrel)) - GetTopTransactionId(); - - /* ready to change the on-disk (or really, in-buffer) tuple */ - START_CRIT_SECTION(); - - /* - * We must mark the buffer dirty before doing XLogInsert(); see notes in - * SyncOneBuffer(). However, we don't apply the desired changes just yet. - * This looks like a violation of the buffer update protocol, but it is in - * fact safe because we hold exclusive lock on the buffer. Any other - * process, including a checkpoint, that tries to examine the buffer - * contents will block until we release the lock, and then will see the - * final state that we install below. - */ - MarkBufferDirty(buf); - - /* XLOG stuff */ - if (logit && RelationNeedsWAL(seqrel)) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - - /* - * We don't log the current state of the tuple, but rather the state - * as it would appear after "log" more fetches. This lets us skip - * that many future WAL records, at the cost that we lose those - * sequence values if we crash. - */ - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - /* set values that will be saved in xlog */ - seq->last_value = next; - seq->is_called = true; - seq->log_cnt = 0; - - xlrec.node = seqrel->rd_node; - - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) seqtuple.t_data, seqtuple.t_len); - - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - /* Now update sequence tuple to the intended final state */ - seq->last_value = last; /* last fetched number */ - seq->is_called = true; - seq->log_cnt = log; /* how much is logged */ - - END_CRIT_SECTION(); - - UnlockReleaseBuffer(buf); - - relation_close(seqrel, NoLock); + sequence_release_tuple(&seqh); + sequence_close(&seqh); return result; } @@ -773,28 +826,27 @@ currval_oid(PG_FUNCTION_ARGS) { Oid relid = PG_GETARG_OID(0); int64 result; - SeqTable elm; - Relation seqrel; + SequenceHandle seqh; /* open and AccessShareLock sequence */ - init_sequence(relid, &elm, &seqrel); + sequence_open(relid, &seqh); - if (pg_class_aclcheck(elm->relid, GetUserId(), + if (pg_class_aclcheck(seqh.elm->relid, GetUserId(), ACL_SELECT | ACL_USAGE) != ACLCHECK_OK) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied for sequence %s", - RelationGetRelationName(seqrel)))); + RelationGetRelationName(seqh.rel)))); - if (!elm->last_valid) + if (!seqh.elm->last_valid) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("currval of sequence \"%s\" is not yet defined in this session", - RelationGetRelationName(seqrel)))); + RelationGetRelationName(seqh.rel)))); - result = elm->last; + result = seqh.elm->last; - relation_close(seqrel, NoLock); + sequence_close(&seqh); PG_RETURN_INT64(result); } @@ -835,31 +887,26 @@ lastval(PG_FUNCTION_ARGS) } /* - * Main internal procedure that handles 2 & 3 arg forms of SETVAL. - * - * Note that the 3 arg version (which sets the is_called flag) is - * only for use in pg_dump, and setting the is_called flag may not - * work if multiple users are attached to the database and referencing - * the sequence (unlikely if pg_dump is restoring it). - * - * It is necessary to have the 3 arg version so that pg_dump can - * restore the state of a sequence exactly during data-only restores - - * it is the only way to clear the is_called flag in an existing - * sequence. + * Implement the setval procedure. */ -static void -do_setval(Oid relid, int64 next, bool iscalled) +Datum +setval_oid(PG_FUNCTION_ARGS) { + Oid relid = PG_GETARG_OID(0); + int64 next = PG_GETARG_INT64(1); SeqTable elm; Relation seqrel; - Buffer buf; - HeapTupleData seqtuple; - Form_pg_sequence seq; + SequenceHandle seqh; + SequenceAM *seqam; /* open and AccessShareLock sequence */ - init_sequence(relid, &elm, &seqrel); + sequence_open(relid, &seqh); + elm = seqh.elm; + seqrel = seqh.rel; + seqam = GetSequenceAMForRelation(seqrel); - if (pg_class_aclcheck(elm->relid, GetUserId(), ACL_UPDATE) != ACLCHECK_OK) + if (pg_class_aclcheck(elm->relid, GetUserId(), + ACL_USAGE | ACL_UPDATE) != ACLCHECK_OK) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied for sequence %s", @@ -876,106 +923,86 @@ do_setval(Oid relid, int64 next, bool iscalled) */ PreventCommandIfParallelMode("setval()"); - /* lock page' buffer and read tuple */ - seq = read_seq_tuple(elm, seqrel, &buf, &seqtuple); - - if ((next < seq->min_value) || (next > seq->max_value)) - { - char bufv[100], - bufm[100], - bufx[100]; - - snprintf(bufv, sizeof(bufv), INT64_FORMAT, next); - snprintf(bufm, sizeof(bufm), INT64_FORMAT, seq->min_value); - snprintf(bufx, sizeof(bufx), INT64_FORMAT, seq->max_value); - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("setval: value %s is out of bounds for sequence \"%s\" (%s..%s)", - bufv, RelationGetRelationName(seqrel), - bufm, bufx))); - } - - /* Set the currval() state only if iscalled = true */ - if (iscalled) - { - elm->last = next; /* last returned number */ - elm->last_valid = true; - } + seqam->Setval(seqrel, &seqh, next); - /* In any case, forget any future cached numbers */ + /* Reset local cached data */ + elm->last = next; /* last returned number */ + elm->last_valid = true; elm->cached = elm->last; - /* check the comment above nextval_internal()'s equivalent call. */ - if (RelationNeedsWAL(seqrel)) - GetTopTransactionId(); - - /* ready to change the on-disk (or really, in-buffer) tuple */ - START_CRIT_SECTION(); - - seq->last_value = next; /* last fetched number */ - seq->is_called = iscalled; - seq->log_cnt = 0; - - MarkBufferDirty(buf); - - /* XLOG stuff */ - if (RelationNeedsWAL(seqrel)) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - Page page = BufferGetPage(buf); - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.node = seqrel->rd_node; - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) seqtuple.t_data, seqtuple.t_len); - - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - END_CRIT_SECTION(); + last_used_seq = elm; - UnlockReleaseBuffer(buf); + sequence_close(&seqh); - relation_close(seqrel, NoLock); + PG_RETURN_INT64(next); } /* - * Implement the 2 arg setval procedure. - * See do_setval for discussion. + * Implement the 3 arg setval procedure. + * + * This is a cludge for supporting old dumps. + * + * Check that the target sequence is local one and then convert this call + * to the seqam_restore call with apropriate data. */ Datum -setval_oid(PG_FUNCTION_ARGS) +setval3_oid(PG_FUNCTION_ARGS) { Oid relid = PG_GETARG_OID(0); int64 next = PG_GETARG_INT64(1); + bool iscalled = PG_GETARG_BOOL(2); + LocalSequenceState state; + SeqTable elm; + Relation seqrel; + SequenceHandle seqh; + SequenceAM *seqam; + + /* open and AccessShareLock sequence */ + sequence_open(relid, &seqh); + elm = seqh.elm; + seqrel = seqh.rel; + seqam = GetSequenceAMForRelation(seqrel); + + if (pg_class_aclcheck(elm->relid, GetUserId(), + ACL_USAGE | ACL_UPDATE) != ACLCHECK_OK) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied for sequence %s", + RelationGetRelationName(seqrel)))); + + /* read-only transactions may only modify temp sequences */ + if (!seqrel->rd_islocaltemp) + PreventCommandIfReadOnly("setval()"); - do_setval(relid, next, true); + /* Make sure the target sequence is 'local' sequence. */ + if (seqrel->rd_rel->relam != LOCAL_SEQAM_OID) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("the setval(oid, bigint, bool) function can only be called for \"local\" sequences"))); + + /* Build the state and pass it to sequence AM. */ + state.last_value = next; + state.log_cnt = 0; + state.is_called = iscalled; + seqam->SetState(seqh.rel, &seqh, PointerGetDatum(&state)); + + /* Set the currval() state only if iscalled = true */ + if (iscalled) + { + elm->last = next; /* last returned number */ + elm->last_valid = true; + } - PG_RETURN_INT64(next); -} + /* Reset local cached data */ + elm->cached = elm->last; -/* - * Implement the 3 arg setval procedure. - * See do_setval for discussion. - */ -Datum -setval3_oid(PG_FUNCTION_ARGS) -{ - Oid relid = PG_GETARG_OID(0); - int64 next = PG_GETARG_INT64(1); - bool iscalled = PG_GETARG_BOOL(2); + last_used_seq = elm; - do_setval(relid, next, iscalled); + sequence_close(&seqh); PG_RETURN_INT64(next); } - /* * Open the sequence and acquire AccessShareLock if needed * @@ -1034,11 +1061,10 @@ create_seq_hashtable(void) } /* - * Given a relation OID, open and lock the sequence. p_elm and p_rel are - * output parameters. + * Given a relation OID, open and share-lock the sequence. */ -static void -init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel) +void +sequence_open(Oid relid, SequenceHandle *seqh) { SeqTable elm; Relation seqrel; @@ -1090,44 +1116,58 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel) } /* Return results */ - *p_elm = elm; - *p_rel = seqrel; + seqh->elm = elm; + seqh->rel = seqrel; + seqh->buf = InvalidBuffer; + seqh->tup.t_data = NULL; + seqh->tup.t_len = 0; + seqh->statetyp = 6025; /* TODO */ + seqh->statetyplen = -1; + seqh->statetypbyval = false; + seqh->inupdate = false; } +/* + * Given the sequence handle, unlock the page buffer and close the relation + */ +void +sequence_close(SequenceHandle *seqh) +{ + Assert(!seqh->inupdate); + + relation_close(seqh->rel, NoLock); +} /* * Given an opened sequence relation, lock the page buffer and find the tuple - * - * *buf receives the reference to the pinned-and-ex-locked buffer - * *seqtuple receives the reference to the sequence tuple proper - * (this arg should point to a local variable of type HeapTupleData) - * - * Function's return value points to the data payload of the tuple */ -static Form_pg_sequence -read_seq_tuple(SeqTable elm, Relation rel, Buffer *buf, HeapTuple seqtuple) +static HeapTuple +sequence_read_tuple(SequenceHandle *seqh) { Page page; + Buffer buf; ItemId lp; sequence_magic *sm; - Form_pg_sequence seq; - *buf = ReadBuffer(rel, 0); - LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); + if (seqh->tup.t_data != NULL) + return &seqh->tup; + + seqh->buf = buf = ReadBuffer(seqh->rel, 0); + LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); - page = BufferGetPage(*buf); + page = BufferGetPage(buf); sm = (sequence_magic *) PageGetSpecialPointer(page); if (sm->magic != SEQ_MAGIC) elog(ERROR, "bad magic number in sequence \"%s\": %08X", - RelationGetRelationName(rel), sm->magic); + RelationGetRelationName(seqh->rel), sm->magic); lp = PageGetItemId(page, FirstOffsetNumber); Assert(ItemIdIsNormal(lp)); - /* Note we currently only bother to set these two fields of *seqtuple */ - seqtuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); - seqtuple->t_len = ItemIdGetLength(lp); + /* Note we currently only bother to set these two fields of the tuple */ + seqh->tup.t_data = (HeapTupleHeader) PageGetItem(page, lp); + seqh->tup.t_len = ItemIdGetLength(lp); /* * Previous releases of Postgres neglected to prevent SELECT FOR UPDATE on @@ -1137,33 +1177,170 @@ read_seq_tuple(SeqTable elm, Relation rel, Buffer *buf, HeapTuple seqtuple) * bit update, ie, don't bother to WAL-log it, since we can certainly do * this again if the update gets lost. */ - Assert(!(seqtuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)); - if (HeapTupleHeaderGetRawXmax(seqtuple->t_data) != InvalidTransactionId) + Assert(!(seqh->tup.t_data->t_infomask & HEAP_XMAX_IS_MULTI)); + if (HeapTupleHeaderGetRawXmax(seqh->tup.t_data) != InvalidTransactionId) + { + HeapTupleHeaderSetXmax(seqh->tup.t_data, InvalidTransactionId); + seqh->tup.t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; + seqh->tup.t_data->t_infomask |= HEAP_XMAX_INVALID; + MarkBufferDirtyHint(buf, true); + } + + /* update our copy of the increment if needed */ + if (seqh->elm->increment == 0) + { + Form_pg_sequence seq = (Form_pg_sequence) GETSTRUCT(&seqh->tup); + seqh->elm->increment = seq->increment_by; + } + + return &seqh->tup; +} + +Form_pg_sequence +sequence_read_options(SequenceHandle *seqh) +{ + return (Form_pg_sequence) GETSTRUCT(sequence_read_tuple(seqh)); +} + +Datum +sequence_read_state(SequenceHandle *seqh) +{ + HeapTuple tup = sequence_read_tuple(seqh); + Form_pg_sequence seq = (Form_pg_sequence) GETSTRUCT(tup); + + return PointerGetDatum(seq->amstate); +} + +/* + * Write a sequence tuple. + * + * If 'do_wal' is false, the update doesn't need to be WAL-logged. After + * a crash, you might get an old copy of the tuple. + * + * We split this into 3 step process so that the tuple may be safely updated + * inline. + */ +void +sequence_start_update(SequenceHandle *seqh, bool dowal) +{ + Assert(seqh->tup.t_data != NULL && !seqh->inupdate); + + + if (seqh->statetyplen < 0) + { + get_typlenbyval(seqh->statetyp, &seqh->statetyplen, + &seqh->statetypbyval); + Assert(seqh->statetyplen > 0); + } + + if (dowal) + GetTopTransactionId(); + + seqh->inupdate = true; + + START_CRIT_SECTION(); +} + +void +sequence_save_state(SequenceHandle *seqh, Datum amstate, bool dowal) +{ + HeapTuple tup = sequence_read_tuple(seqh); + Form_pg_sequence seq = (Form_pg_sequence) GETSTRUCT(tup); + Page page; + + /* + * Update the state data inline. + * + * This is only needed when the provided amstate datum points to different + * data than what is already in the tuple. + */ + if (DatumGetPointer(amstate) != seq->amstate) + { + if (seqh->statetypbyval) + store_att_byval(seq->amstate, amstate, seqh->statetyplen); + else + memmove(seq->amstate, DatumGetPointer(amstate), seqh->statetyplen); + } + + page = BufferGetPage(seqh->buf); + MarkBufferDirtyHint(seqh->buf, true); + + if (dowal && RelationNeedsWAL(seqh->rel)) + log_sequence_tuple(seqh->rel, &seqh->tup, seqh->buf, page); +} + +void +sequence_finish_update(SequenceHandle *seqh) +{ + Assert(seqh->inupdate); + + END_CRIT_SECTION(); + + seqh->inupdate = false; +} + + +/* + * Release a tuple, read with sequence_read_tuple, without saving it + */ +void +sequence_release_tuple(SequenceHandle *seqh) +{ + /* Remove the tuple from cache */ + if (seqh->tup.t_data != NULL) + { + seqh->tup.t_data = NULL; + seqh->tup.t_len = 0; + } + + /* Release the page lock */ + if (BufferIsValid(seqh->buf)) { - HeapTupleHeaderSetXmax(seqtuple->t_data, InvalidTransactionId); - seqtuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; - seqtuple->t_data->t_infomask |= HEAP_XMAX_INVALID; - MarkBufferDirtyHint(*buf, true); + UnlockReleaseBuffer(seqh->buf); + seqh->buf = InvalidBuffer; } +} + +/* + * Returns true, if the next update to the sequence tuple needs to be + * WAL-logged because it's the first update after a checkpoint. + * + * The sequence AM can use this as a hint, if it wants to piggyback some extra + * actions on WAL-logged updates. + * + * NB: This is just a hint. even when sequence_needs_wal() returns 'false', + * the sequence access method might decide to WAL-log an update anyway. + */ +bool +sequence_needs_wal(SequenceHandle *seqh) +{ + Page page; + XLogRecPtr redoptr; - seq = (Form_pg_sequence) GETSTRUCT(seqtuple); + Assert(BufferIsValid(seqh->buf)); - /* this is a handy place to update our copy of the increment */ - elm->increment = seq->increment_by; + if (!RelationNeedsWAL(seqh->rel)) + return false; - return seq; + page = BufferGetPage(seqh->buf); + redoptr = GetRedoRecPtr(); + + return (PageGetLSN(page) <= redoptr); } /* - * 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 params with default values; + * otherwise, do not change existing params that aren't explicitly overridden. * - * If isInit is true, fill any unspecified options with default values; - * otherwise, do not change existing options that aren't explicitly overridden. + * Note that only syntax check is done for RESTART [WITH] parameter, the actual + * handling of it should be done by init function of a sequence access method. */ 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; @@ -1173,13 +1350,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) { @@ -1250,13 +1427,6 @@ init_params(List *options, bool isInit, defel->defname); } - /* - * We must reset log_cnt when isInit or when changing any parameters that - * would affect future nextval allocations. - */ - if (isInit) - new->log_cnt = 0; - /* INCREMENT BY */ if (increment_by != NULL) { @@ -1265,7 +1435,6 @@ init_params(List *options, bool isInit, ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("INCREMENT must not be zero"))); - new->log_cnt = 0; } else if (isInit) new->increment_by = 1; @@ -1275,7 +1444,6 @@ init_params(List *options, bool isInit, { new->is_cycled = intVal(is_cycled->arg); Assert(BoolIsValid(new->is_cycled)); - new->log_cnt = 0; } else if (isInit) new->is_cycled = false; @@ -1284,7 +1452,6 @@ init_params(List *options, bool isInit, if (max_value != NULL && max_value->arg) { new->max_value = defGetInt64(max_value); - new->log_cnt = 0; } else if (isInit || max_value != NULL) { @@ -1292,14 +1459,12 @@ init_params(List *options, bool isInit, new->max_value = SEQ_MAXVALUE; /* ascending seq */ else new->max_value = -1; /* descending seq */ - new->log_cnt = 0; } /* MINVALUE (null arg means NO MINVALUE) */ if (min_value != NULL && min_value->arg) { new->min_value = defGetInt64(min_value); - new->log_cnt = 0; } else if (isInit || min_value != NULL) { @@ -1307,7 +1472,6 @@ init_params(List *options, bool isInit, new->min_value = 1; /* ascending seq */ else new->min_value = SEQ_MINVALUE; /* descending seq */ - new->log_cnt = 0; } /* crosscheck min/max */ @@ -1361,48 +1525,6 @@ init_params(List *options, bool isInit, bufs, bufm))); } - /* RESTART [WITH] */ - if (restart_value != NULL) - { - if (restart_value->arg != NULL) - new->last_value = defGetInt64(restart_value); - else - new->last_value = new->start_value; - new->is_called = false; - new->log_cnt = 0; - } - else if (isInit) - { - new->last_value = new->start_value; - new->is_called = false; - } - - /* crosscheck RESTART (or current value, if changing MIN/MAX) */ - if (new->last_value < new->min_value) - { - char bufs[100], - bufm[100]; - - snprintf(bufs, sizeof(bufs), INT64_FORMAT, new->last_value); - snprintf(bufm, sizeof(bufm), INT64_FORMAT, new->min_value); - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("RESTART value (%s) cannot be less than MINVALUE (%s)", - bufs, bufm))); - } - if (new->last_value > new->max_value) - { - char bufs[100], - bufm[100]; - - snprintf(bufs, sizeof(bufs), INT64_FORMAT, new->last_value); - snprintf(bufm, sizeof(bufm), INT64_FORMAT, new->max_value); - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("RESTART value (%s) cannot be greater than MAXVALUE (%s)", - bufs, bufm))); - } - /* CACHE */ if (cache_value != NULL) { @@ -1417,7 +1539,6 @@ init_params(List *options, bool isInit, errmsg("CACHE (%s) must be greater than zero", buf))); } - new->log_cnt = 0; } else if (isInit) new->cache_value = 1; @@ -1528,20 +1649,17 @@ pg_sequence_parameters(PG_FUNCTION_ARGS) TupleDesc tupdesc; Datum values[5]; bool isnull[5]; - SeqTable elm; - Relation seqrel; - Buffer buf; - HeapTupleData seqtuple; Form_pg_sequence seq; + SequenceHandle seqh; /* open and AccessShareLock sequence */ - init_sequence(relid, &elm, &seqrel); + sequence_open(relid, &seqh); if (pg_class_aclcheck(relid, GetUserId(), ACL_SELECT | ACL_UPDATE | ACL_USAGE) != ACLCHECK_OK) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied for sequence %s", - RelationGetRelationName(seqrel)))); + RelationGetRelationName(seqh.rel)))); tupdesc = CreateTemplateTupleDesc(5, false); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "start_value", @@ -1559,7 +1677,7 @@ pg_sequence_parameters(PG_FUNCTION_ARGS) memset(isnull, 0, sizeof(isnull)); - seq = read_seq_tuple(elm, seqrel, &buf, &seqtuple); + seq = (Form_pg_sequence) GETSTRUCT(sequence_read_tuple(&seqh)); values[0] = Int64GetDatum(seq->start_value); values[1] = Int64GetDatum(seq->min_value); @@ -1567,12 +1685,85 @@ pg_sequence_parameters(PG_FUNCTION_ARGS) values[3] = Int64GetDatum(seq->increment_by); values[4] = BoolGetDatum(seq->is_cycled); - UnlockReleaseBuffer(buf); - relation_close(seqrel, NoLock); + sequence_release_tuple(&seqh); + sequence_close(&seqh); return HeapTupleGetDatum(heap_form_tuple(tupdesc, values, isnull)); } +Datum +pg_sequence_get_state(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + Datum state; + char *statestr; + SequenceHandle seqh; + SequenceAM *seqam; + Oid typoutput; + bool typisvarlena; + + /* Load the sequence AM */ + sequence_open(relid, &seqh); + seqam = GetSequenceAMForRelation(seqh.rel); + + /* Get the type output function. */ + getTypeOutputInfo(seqam->StateTypeOid, &typoutput, &typisvarlena); + + /* Get the output and convert it to string. */ + state = seqam->GetState(seqh.rel, &seqh); + statestr = OidOutputFunctionCall(typoutput, state); + + sequence_close(&seqh); + + PG_RETURN_TEXT_P(cstring_to_text(statestr)); +} + +Datum +pg_sequence_set_state(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + char *statestr = text_to_cstring(PG_GETARG_TEXT_PP(1)); + SequenceHandle seqh; + SequenceAM *seqam; + Oid typinput, + typioparam; + Datum state; + + /* Load the sequence AM */ + sequence_open(relid, &seqh); + seqam = GetSequenceAMForRelation(seqh.rel); + + /* Get the type input function. */ + getTypeInputInfo(seqam->StateTypeOid, &typinput, &typioparam); + + /* Convert the string to the state type and set it as new state. */ + state = OidInputFunctionCall(typinput, statestr, typioparam, -1); + seqam->SetState(seqh.rel, &seqh, state); + + sequence_close(&seqh); + + PG_RETURN_VOID(); +} + +static void +log_sequence_tuple(Relation seqrel, HeapTuple tuple, + Buffer buf, Page page) +{ + xl_seq_rec xlrec; + XLogRecPtr recptr; + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.node = seqrel->rd_node; + + XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); + XLogRegisterData((char *) tuple->t_data, tuple->t_len); + + recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); + + PageSetLSN(page, recptr); +} void seq_redo(XLogReaderState *record) @@ -1638,3 +1829,149 @@ ResetSequenceCaches(void) last_used_seq = NULL; } + +/* + * Increment sequence while correctly handling overflows and min/max. + */ +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; +} + + +/* + * Check that new value, minimum and maximum are valid. + * + * Used by sequence AMs during sequence initialization to validate + * the sequence parameters. + */ +void +sequence_check_range(int64 value, int64 min_value, int64 max_value, const char *valname) +{ + if (value < min_value) + { + char bufs[100], + bufm[100]; + + snprintf(bufs, sizeof(bufs), INT64_FORMAT, value); + snprintf(bufm, sizeof(bufm), INT64_FORMAT, min_value); + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s value (%s) cannot be less than MINVALUE (%s)", + valname, bufs, bufm))); + } + + if (value > max_value) + { + char bufs[100], + bufm[100]; + + snprintf(bufs, sizeof(bufs), INT64_FORMAT, value); + snprintf(bufm, sizeof(bufm), INT64_FORMAT, max_value); + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s value (%s) cannot be greater than MAXVALUE (%s)", + valname, bufs, bufm))); + } + +} + +/* + * It's reasonable to expect many sequence AMs to care only about + * RESTART [WITH] option of ALTER SEQUENCE command, so we provide + * this interface for convenience. + * It is also useful for ALTER SEQUENCE USING. + */ +int64 +sequence_get_restart_value(List *options, int64 default_value, bool *found) +{ + ListCell *opt; + + foreach(opt, options) + { + DefElem *defel = (DefElem *) lfirst(opt); + + if (strcmp(defel->defname, "restart") == 0) + { + *found = true; + if (defel->arg != NULL) + return defGetInt64(defel); + else + return default_value; + } + } + + *found = false; + return default_value; +} diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index ba0a7ec..bcd2c32 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -19,6 +19,7 @@ #include "access/multixact.h" #include "access/reloptions.h" #include "access/relscan.h" +#include "access/seqam.h" #include "access/sysattr.h" #include "access/xact.h" #include "access/xlog.h" @@ -267,6 +268,7 @@ struct DropRelationCallbackState #define ATT_INDEX 0x0008 #define ATT_COMPOSITE_TYPE 0x0010 #define ATT_FOREIGN_TABLE 0x0020 +#define ATT_SEQUENCE 0x0040 static void truncate_check_rel(Relation rel); static List *MergeAttributes(List *schema, List *supers, char relpersistence, @@ -452,7 +454,7 @@ static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, * ---------------------------------------------------------------- */ ObjectAddress -DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, +DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, Oid relamid, ObjectAddress *typaddress) { char relname[NAMEDATALEN]; @@ -471,7 +473,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, Datum reloptions; ListCell *listptr; AttrNumber attnum; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; @@ -549,13 +550,29 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, /* * Parse and validate reloptions, if any. */ - reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps, - true, false); + if (relkind == RELKIND_SEQUENCE) + { + SequenceAM *seqam; + + Assert(relamid != InvalidOid); + seqam = GetSequenceAMByAMId(relamid); + reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, + NULL, true, false); - if (relkind == RELKIND_VIEW) - (void) view_reloptions(reloptions, true); + (void) am_reloptions(seqam->ParseRelOption, reloptions, true); + } else - (void) heap_reloptions(relkind, reloptions, true); + { + static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + + Assert(relamid == InvalidOid); + reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, + validnsps, true, false); + if (relkind == RELKIND_VIEW) + (void) view_reloptions(reloptions, true); + else + (void) heap_reloptions(relkind, reloptions, true); + } if (stmt->ofTypename) { @@ -676,6 +693,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, true, allowSystemTableMods, false, + relamid, typaddress); /* Store inheritance information for new rel. */ @@ -3306,7 +3324,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, case AT_SetRelOptions: /* SET (...) */ case AT_ResetRelOptions: /* RESET (...) */ case AT_ReplaceRelOptions: /* reset them all, then set just these */ - ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_MATVIEW | ATT_INDEX); + ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_MATVIEW | ATT_INDEX | ATT_SEQUENCE); /* This command never recurses */ /* No command-specific prep needed */ pass = AT_PASS_MISC; @@ -4306,6 +4324,9 @@ ATSimplePermissions(Relation rel, int allowed_targets) case RELKIND_FOREIGN_TABLE: actual_target = ATT_FOREIGN_TABLE; break; + case RELKIND_SEQUENCE: + actual_target = ATT_SEQUENCE; + break; default: actual_target = 0; break; @@ -4349,8 +4370,8 @@ ATWrongRelkindError(Relation rel, int allowed_targets) case ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE: msg = _("\"%s\" is not a table, view, or foreign table"); break; - case ATT_TABLE | ATT_VIEW | ATT_MATVIEW | ATT_INDEX: - msg = _("\"%s\" is not a table, view, materialized view, or index"); + case ATT_TABLE | ATT_VIEW | ATT_MATVIEW | ATT_INDEX | ATT_SEQUENCE: + msg = _("\"%s\" is not a table, view, materialized view, index or sequence"); break; case ATT_TABLE | ATT_MATVIEW: msg = _("\"%s\" is not a table or materialized view"); @@ -9401,7 +9422,10 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, (void) view_reloptions(newOptions, true); break; case RELKIND_INDEX: - (void) index_reloptions(rel->amroutine->amoptions, newOptions, true); + (void) am_reloptions(rel->amroutine->amoptions, newOptions, true); + break; + case RELKIND_SEQUENCE: + (void) am_reloptions(rel->rd_seqam->ParseRelOption, newOptions, true); break; default: ereport(ERROR, diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 8d8731d..a706331 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -2113,7 +2113,8 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist) /* * Finally create the relation. This also creates the type. */ - DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address); + DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, InvalidOid, + &address); return address; } diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c index efa4be1..3cea360 100644 --- a/src/backend/commands/view.c +++ b/src/backend/commands/view.c @@ -240,7 +240,8 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace, * existing view, so we don't need more code to complain if "replace" * is false). */ - address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL); + address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, + InvalidOid, NULL); Assert(address.objectId != InvalidOid); return address; } diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 4cf14b6..a4e235b 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3556,7 +3556,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; @@ -3569,7 +3571,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 a13d831..8fbf26d 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1606,7 +1606,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; @@ -1617,7 +1619,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 223ef17..d23fc22 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -3585,7 +3585,33 @@ CreateSeqStmt: CreateSeqStmt *n = makeNode(CreateSeqStmt); $4->relpersistence = $2; n->sequence = $4; + n->accessMethod = NULL; 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; @@ -3607,7 +3633,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; } @@ -3615,11 +3665,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; } @@ -3678,7 +3751,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 7f1c28f..82e3bce 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -29,6 +29,7 @@ #include "access/amapi.h" #include "access/htup_details.h" #include "access/reloptions.h" +#include "access/seqam.h" #include "catalog/dependency.h" #include "catalog/heap.h" #include "catalog/index.h" @@ -454,6 +455,8 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) seqstmt = makeNode(CreateSeqStmt); seqstmt->sequence = makeRangeVar(snamespace, sname, -1); seqstmt->options = NIL; + seqstmt->amoptions = NIL; + seqstmt->accessMethod = NULL; /* * If this is ALTER ADD COLUMN, make sure the sequence will be owned @@ -1701,7 +1704,8 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt) * else dump and reload will produce a different index (breaking * pg_upgrade in particular). */ - if (index_rel->rd_rel->relam != get_am_oid(DEFAULT_INDEX_TYPE, false)) + if (index_rel->rd_rel->relam != get_index_am_oid(DEFAULT_INDEX_TYPE, + false)) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("index \"%s\" is not a btree", index_name), diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index e81bbc6..e687caf 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -965,7 +965,8 @@ ProcessUtilitySlow(Node *parsetree, /* Create the table itself */ address = DefineRelation((CreateStmt *) stmt, RELKIND_RELATION, - InvalidOid, NULL); + InvalidOid, InvalidOid, + NULL); EventTriggerCollectSimpleCommand(address, secondaryObject, stmt); @@ -998,7 +999,8 @@ ProcessUtilitySlow(Node *parsetree, /* Create the table itself */ address = DefineRelation((CreateStmt *) stmt, RELKIND_FOREIGN_TABLE, - InvalidOid, NULL); + InvalidOid, InvalidOid, + NULL); CreateForeignTable((CreateForeignTableStmt *) stmt, address.objectId); EventTriggerCollectSimpleCommand(address, diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c index 577c059..fed33c4 100644 --- a/src/backend/utils/cache/catcache.c +++ b/src/backend/utils/cache/catcache.c @@ -999,7 +999,7 @@ InitCatCachePhase2(CatCache *cache, bool touch_index) if (touch_index && cache->id != AMOID && - cache->id != AMNAME) + cache->id != AMNAMEKIND) { Relation idesc; @@ -1060,7 +1060,7 @@ IndexScanOK(CatCache *cache, ScanKey cur_skey) break; case AMOID: - case AMNAME: + case AMNAMEKIND: /* * Always do heap scans in pg_am, because it's so small there's diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 7d19b45..5a2720a 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -33,6 +33,7 @@ #include "access/htup_details.h" #include "access/multixact.h" #include "access/reloptions.h" +#include "access/seqam.h" #include "access/sysattr.h" #include "access/transam.h" #include "access/xact.h" @@ -258,6 +259,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); @@ -421,6 +423,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; + amoptions_function amoptions = NULL; relation->rd_options = NULL; @@ -429,10 +432,15 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple) { case RELKIND_RELATION: case RELKIND_TOASTVALUE: - case RELKIND_INDEX: case RELKIND_VIEW: case RELKIND_MATVIEW: break; + case RELKIND_INDEX: + amoptions = relation->amroutine->amoptions; + break; + case RELKIND_SEQUENCE: + amoptions = relation->rd_seqam->ParseRelOption; + break; default: return; } @@ -443,9 +451,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple) * code for pg_class itself) */ options = extractRelOptions(tuple, - GetPgClassDescriptor(), - relation->rd_rel->relkind == RELKIND_INDEX ? - relation->amroutine->amoptions : NULL); + GetPgClassDescriptor(), amoptions); /* * Copy parsed data into CacheMemoryContext. To guard against the @@ -1046,11 +1052,14 @@ RelationBuildDesc(Oid targetRelId, bool insertIt) else relation->rd_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); @@ -1595,6 +1604,31 @@ LookupOpclassInfo(Oid operatorClassOid, return opcentry; } +/* + * Initialize sequence-access-method support data for a sequence relation + */ +static void +RelationInitSequenceAccessInfo(Relation rel) +{ + HeapTuple tuple; + Form_pg_am aform; + SequenceAM *result, *tmp; + + tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(rel->rd_rel->relam)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", + rel->rd_rel->relam); + aform = (Form_pg_am) MemoryContextAlloc(CacheMemoryContext, sizeof *aform); + memcpy(aform, GETSTRUCT(tuple), sizeof *aform); + ReleaseSysCache(tuple); + rel->rd_am = aform; + + result = (SequenceAM *) MemoryContextAlloc(CacheMemoryContext, + sizeof(SequenceAM)); + tmp = GetSequenceAM(rel->rd_am->amhandler); + memcpy(result, tmp, sizeof(SequenceAM)); + rel->rd_seqam = result; +} /* * formrdesc @@ -2081,6 +2115,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc) pfree(relation->rd_indextuple); if (relation->rd_am) pfree(relation->rd_am); + if (relation->rd_seqam) + pfree(relation->rd_seqam); if (relation->rd_indexcxt) MemoryContextDelete(relation->rd_indexcxt); if (relation->rd_rulescxt) @@ -4927,6 +4963,7 @@ load_relcache_init_file(bool shared) Assert(rel->rd_supportinfo == NULL); Assert(rel->rd_indoption == NULL); Assert(rel->rd_indcollation == NULL); + Assert(rel->rd_seqam == NULL); } /* diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index efce7b9..167ea03 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -126,12 +126,12 @@ static const struct cachedesc cacheinfo[] = { }, 16 }, - {AccessMethodRelationId, /* AMNAME */ - AmNameIndexId, - 1, + {AccessMethodRelationId, /* AMNAMEKIND */ + AmNameKindIndexId, + 2, { Anum_pg_am_amname, - 0, + Anum_pg_am_amkind, 0, 0 }, diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index a185749..0509d25 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -28,6 +28,7 @@ #include "access/commit_ts.h" #include "access/gin.h" +#include "access/seqam.h" #include "access/transam.h" #include "access/twophase.h" #include "access/xact.h" diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 36863df..52f2961 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -45,6 +45,7 @@ #include "access/attnum.h" #include "access/sysattr.h" #include "access/transam.h" +#include "catalog/pg_am.h" #include "catalog/pg_cast.h" #include "catalog/pg_class.h" #include "catalog/pg_default_acl.h" @@ -4552,6 +4553,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) int i_relreplident; int i_owning_tab; int i_owning_col; + int i_relam; int i_reltablespace; int i_reloptions; int i_checkoption; @@ -4603,6 +4605,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, " "d.refobjid AS owning_tab, " "d.refobjsubid AS owning_col, " + "c.relam, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, " "array_to_string(array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded'), ', ') AS reloptions, " "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text " @@ -4645,6 +4648,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, " "d.refobjid AS owning_tab, " "d.refobjsubid AS owning_col, " + "c.relam, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, " "array_to_string(array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded'), ', ') AS reloptions, " "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text " @@ -4687,6 +4691,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, " "d.refobjid AS owning_tab, " "d.refobjsubid AS owning_col, " + "c.relam, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, " "array_to_string(array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded'), ', ') AS reloptions, " "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text " @@ -4729,6 +4734,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, " "d.refobjid AS owning_tab, " "d.refobjsubid AS owning_col, " + "c.relam, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, " "array_to_string(c.reloptions, ', ') AS reloptions, " "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions " @@ -4769,6 +4775,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, " "d.refobjid AS owning_tab, " "d.refobjsubid AS owning_col, " + "c.relam, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, " "array_to_string(c.reloptions, ', ') AS reloptions, " "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions " @@ -4808,6 +4815,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "NULL AS reloftype, " "d.refobjid AS owning_tab, " "d.refobjsubid AS owning_col, " + "c.relam, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, " "array_to_string(c.reloptions, ', ') AS reloptions, " "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions " @@ -4847,6 +4855,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "NULL AS reloftype, " "d.refobjid AS owning_tab, " "d.refobjsubid AS owning_col, " + "c.relam, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, " "array_to_string(c.reloptions, ', ') AS reloptions, " "NULL AS toast_reloptions " @@ -4886,6 +4895,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "NULL AS reloftype, " "d.refobjid AS owning_tab, " "d.refobjsubid AS owning_col, " + "c.relam, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, " "NULL AS reloptions, " "NULL AS toast_reloptions " @@ -4924,6 +4934,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "NULL AS reloftype, " "d.refobjid AS owning_tab, " "d.refobjsubid AS owning_col, " + "c.relam, " "NULL AS reltablespace, " "NULL AS reloptions, " "NULL AS toast_reloptions " @@ -4958,6 +4969,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "NULL AS reloftype, " "NULL::oid AS owning_tab, " "NULL::int4 AS owning_col, " + "c.relam, " "NULL AS reltablespace, " "NULL AS reloptions, " "NULL AS toast_reloptions " @@ -4987,6 +4999,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "NULL AS reloftype, " "NULL::oid AS owning_tab, " "NULL::int4 AS owning_col, " + "c.relam, " "NULL AS reltablespace, " "NULL AS reloptions, " "NULL AS toast_reloptions " @@ -5026,6 +5039,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "NULL AS reloftype, " "NULL::oid AS owning_tab, " "NULL::int4 AS owning_col, " + "c.relam, " "NULL AS reltablespace, " "NULL AS reloptions, " "NULL AS toast_reloptions " @@ -5079,6 +5093,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) i_relpages = PQfnumber(res, "relpages"); i_owning_tab = PQfnumber(res, "owning_tab"); i_owning_col = PQfnumber(res, "owning_col"); + i_relam = PQfnumber(res, "relam"); i_reltablespace = PQfnumber(res, "reltablespace"); i_reloptions = PQfnumber(res, "reloptions"); i_checkoption = PQfnumber(res, "checkoption"); @@ -5144,6 +5159,10 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) tblinfo[i].owning_tab = atooid(PQgetvalue(res, i, i_owning_tab)); tblinfo[i].owning_col = atoi(PQgetvalue(res, i, i_owning_col)); } + if (PQgetisnull(res, i, i_relam)) + tblinfo[i].relam = InvalidOid; + else + tblinfo[i].relam = atoi(PQgetvalue(res, i, i_relam)); tblinfo[i].reltablespace = pg_strdup(PQgetvalue(res, i, i_reltablespace)); tblinfo[i].reloptions = pg_strdup(PQgetvalue(res, i, i_reloptions)); if (i_checkoption == -1 || PQgetisnull(res, i, i_checkoption)) @@ -14956,7 +14975,8 @@ dumpSequence(Archive *fout, DumpOptions *dopt, TableInfo *tbinfo) *incby, *maxv = NULL, *minv = NULL, - *cache; + *cache, + *amname = NULL; char bufm[100], bufx[100]; bool cycled; @@ -15036,6 +15056,37 @@ dumpSequence(Archive *fout, DumpOptions *dopt, TableInfo *tbinfo) cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0); /* + * 9.6 adds sequence access methods but we only care if valid + * sequence am that is not the default one is specified. + */ + if (fout->remoteVersion >= 90600 && + tbinfo->relam != InvalidOid && + tbinfo->relam != LOCAL_SEQAM_OID) + { + PGresult *res2; + + printfPQExpBuffer(query, "SELECT a.amname\n" + "FROM pg_catalog.pg_am a, pg_catalog.pg_class c\n" + "WHERE c.relam = a.oid AND c.oid = %u", + tbinfo->dobj.catId.oid); + + res2 = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + + if (PQntuples(res2) != 1) + { + write_msg(NULL, ngettext("query to get access method of sequence \"%s\" returned %d row (expected 1)\n", + "query to get access method of sequence \"%s\" returned %d rows (expected 1)\n", + PQntuples(res2)), + tbinfo->dobj.name, PQntuples(res2)); + exit_nicely(1); + } + + amname = pg_strdup(PQgetvalue(res2, 0, 0)); + + PQclear(res2); + } + + /* * DROP must be fully qualified in case same name appears in pg_catalog */ appendPQExpBuffer(delqry, "DROP SEQUENCE %s.", @@ -15076,6 +15127,13 @@ dumpSequence(Archive *fout, DumpOptions *dopt, TableInfo *tbinfo) " CACHE %s%s", cache, (cycled ? "\n CYCLE" : "")); + /* + * Only produce using when it makes sense, + * this helps with backwards compatibility. + */ + if (amname) + appendPQExpBuffer(query, "\n USING %s", fmtId(amname)); + appendPQExpBufferStr(query, ";\n"); appendPQExpBuffer(labelq, "SEQUENCE %s", fmtId(tbinfo->dobj.name)); @@ -15142,6 +15200,9 @@ dumpSequence(Archive *fout, DumpOptions *dopt, TableInfo *tbinfo) tbinfo->dobj.namespace->dobj.name, tbinfo->rolname, tbinfo->dobj.catId, 0, tbinfo->dobj.dumpId); + if (amname) + free(amname); + PQclear(res); destroyPQExpBuffer(query); @@ -15158,16 +15219,29 @@ dumpSequenceData(Archive *fout, TableDataInfo *tdinfo) { TableInfo *tbinfo = tdinfo->tdtable; PGresult *res; - char *last; - bool called; PQExpBuffer query = createPQExpBuffer(); /* Make sure we are in proper schema */ selectSourceSchema(fout, tbinfo->dobj.namespace->dobj.name); - appendPQExpBuffer(query, - "SELECT last_value, is_called FROM %s", - fmtId(tbinfo->dobj.name)); + /* + * On 9.6 there is special interface for dumping sequences but we only + * use it for one with nondefault access method because we can produce + * more backward compatible dump that way. + */ + if (fout->remoteVersion >= 90600 && + tbinfo->relam != InvalidOid && + tbinfo->relam != LOCAL_SEQAM_OID) + { + appendPQExpBuffer(query, + "SELECT quote_literal(pg_catalog.pg_sequence_get_state("); + appendStringLiteralAH(query, tbinfo->dobj.name, fout); + appendPQExpBuffer(query, "))"); + } + else + appendPQExpBuffer(query, + "SELECT last_value, is_called FROM %s", + fmtId(tbinfo->dobj.name)); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -15180,14 +15254,29 @@ dumpSequenceData(Archive *fout, TableDataInfo *tdinfo) exit_nicely(1); } - last = PQgetvalue(res, 0, 0); - called = (strcmp(PQgetvalue(res, 0, 1), "t") == 0); - resetPQExpBuffer(query); - appendPQExpBufferStr(query, "SELECT pg_catalog.setval("); - appendStringLiteralAH(query, fmtId(tbinfo->dobj.name), fout); - appendPQExpBuffer(query, ", %s, %s);\n", - last, (called ? "true" : "false")); + + if (fout->remoteVersion >= 90600 && + tbinfo->relam != InvalidOid && + tbinfo->relam != LOCAL_SEQAM_OID) + { + char *state = PQgetvalue(res, 0, 0); + + appendPQExpBufferStr(query, "SELECT pg_catalog.pg_sequence_set_state("); + appendStringLiteralAH(query, fmtId(tbinfo->dobj.name), fout); + /* The state got quote in the SELECT. */ + appendPQExpBuffer(query, ", %s);\n", state); + } + else + { + char *last = PQgetvalue(res, 0, 0); + bool called = (strcmp(PQgetvalue(res, 0, 1), "t") == 0); + + appendPQExpBufferStr(query, "SELECT pg_catalog.setval("); + appendStringLiteralAH(query, fmtId(tbinfo->dobj.name), fout); + appendPQExpBuffer(query, ", %s, %s);\n", + last, (called ? "true" : "false")); + } ArchiveEntry(fout, nilCatalogId, createDumpId(), tbinfo->dobj.name, diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index cb88b82..580d28f 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -223,6 +223,7 @@ typedef struct _tableInfo /* these two are set only if table is a sequence owned by a column: */ Oid owning_tab; /* OID of table owning sequence */ int owning_col; /* attr # of column owning sequence */ + int relam; /* access method (from pg_clas) */ int relpages; /* table's size in pages (from pg_class) */ bool interesting; /* true if need to collect more data */ diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index bb59bc2..9105cff 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -1403,30 +1403,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); - 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 @@ -1470,13 +1446,55 @@ 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); + + /* + * For sequence, fetch only the common column unless verbose was specified. + * Note that this is change from pre9.5 versions. + */ + if (tableinfo.relkind == 'S') + appendPQExpBufferStr(&buf, " AND attname <> 'amdata'"); + appendPQExpBufferStr(&buf, "\nORDER BY a.attnum;"); + res = PSQLexec(buf.data); if (!res) 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); + 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) { @@ -1805,6 +1823,8 @@ describeOneTableDetails(const char *schemaname, oid); result = PSQLexec(buf.data); + + /* Same logic as above, only print result when we get one row. */ if (!result) goto error_return; else if (PQntuples(result) == 1) @@ -1814,12 +1834,56 @@ describeOneTableDetails(const char *schemaname, printTableAddFooter(&cont, buf.data); } + PQclear(result); + + /* Get the Access Method name for the sequence */ + printfPQExpBuffer(&buf, "SELECT a.amname\n" + "FROM pg_catalog.pg_am a, pg_catalog.pg_class c\n" + "WHERE c.relam = a.oid AND c.oid = %s", oid); + + result = PSQLexec(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. */ + 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); + + if (verbose) + { + /* Get the Access Method state */ + printfPQExpBuffer(&buf, + "SELECT pg_catalog.pg_sequence_get_state('%s');", + oid); + + result = PSQLexec(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. + */ + if (!result) + goto error_return; + else if (PQntuples(result) == 1) + { + printfPQExpBuffer(&buf, _("Access Method State: %s"), + PQgetvalue(result, 0, 0)); + printTableAddFooter(&cont, buf.data); + } + + PQclear(result); + } } else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' || tableinfo.relkind == 'f') diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 7de02d2..f54af37 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -273,7 +273,7 @@ extern bytea *default_reloptions(Datum reloptions, bool validate, relopt_kind kind); extern bytea *heap_reloptions(char relkind, Datum reloptions, bool validate); extern bytea *view_reloptions(Datum reloptions, bool validate); -extern bytea *index_reloptions(amoptions_function amoptions, Datum reloptions, +extern bytea *am_reloptions(amoptions_function 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..9edebee --- /dev/null +++ b/src/include/access/seqam.h @@ -0,0 +1,109 @@ +/*------------------------------------------------------------------------- + * + * seqam.h + * Public header file for Sequence access method. + * + * + * Portions Copyright (c) 1996-2015, 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" + +struct SequenceHandle; +typedef struct SequenceHandle SequenceHandle; + +typedef bytea * (*ParseRelOptions_function) (Datum reloptions, + bool validate); + +typedef Datum (*SeqAMInit_function) (Relation seqrel, + Form_pg_sequence seq, + int64 restart_value, + bool restart_requested, + bool is_init); + +typedef int64 (*SeqAMAlloc_function) (Relation seqrel, + SequenceHandle *seqh, + int64 nrequested, + int64 *last); + +typedef void (*SeqAMSetval_function) (Relation seqrel, + SequenceHandle *seqh, + int64 new_value); + +typedef Datum (*SeqAMGetState_function) (Relation seqrel, + SequenceHandle *seqh); +typedef void (*SeqAMSetState_function) (Relation seqrel, SequenceHandle *seqh, + Datum amstate); + +typedef struct SequenceAM +{ + NodeTag type; + + /* Custom columns needed by the AM */ + Oid StateTypeOid; + + /* Function for parsing reloptions */ + ParseRelOptions_function ParseRelOption; + + /* Initialization */ + SeqAMInit_function Init; + + /* nextval handler */ + SeqAMAlloc_function Alloc; + + /* State manipulation functions */ + SeqAMSetval_function Setval; + SeqAMGetState_function GetState; + SeqAMSetState_function SetState; +} SequenceAM; + +extern Oid get_seqam_oid(const char *sequencename, bool missing_ok); +extern SequenceAM *GetSequenceAM(Oid seqamhandler); +extern SequenceAM *GetSequenceAMByAMId(Oid amoid); +extern SequenceAM *GetSequenceAMForRelation(Relation relation); + + +extern void sequence_open(Oid seqrelid, SequenceHandle *seqh); +extern void sequence_close(SequenceHandle *seqh); +extern Form_pg_sequence sequence_read_options(SequenceHandle *seqh); +extern Datum sequence_read_state(SequenceHandle *seqh); +extern void sequence_start_update(SequenceHandle *seqh, bool dowal); +extern void sequence_save_state(SequenceHandle *seqh, Datum seqstate, bool dowal); +extern void sequence_finish_update(SequenceHandle *seqh); +extern void sequence_release_tuple(SequenceHandle *seqh); +extern bool sequence_needs_wal(SequenceHandle *seqh); + +extern int64 sequence_increment(Relation seqrel, int64 *value, int64 incnum, + int64 minv, int64 maxv, int64 incby, + bool is_cycled, bool report_errors); +extern void sequence_check_range(int64 value, int64 min_value, + int64 max_value, const char *valname); +extern int64 sequence_get_restart_value(List *options, int64 default_value, + bool *found); + +/* TODO move to seqamlocal.h */ +typedef struct LocalSequenceState +{ + int64 last_value; + int32 log_cnt; + bool is_called; +} LocalSequenceState; + +extern Datum seqam_local_state_in(PG_FUNCTION_ARGS); +extern Datum seqam_local_state_out(PG_FUNCTION_ARGS); +extern Datum seqam_local_handler(PG_FUNCTION_ARGS); + +#endif /* SEQAM_H */ diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index e6ac394..ce0ba57 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -71,6 +71,7 @@ extern Oid heap_create_with_catalog(const char *relname, bool use_user_acl, bool allow_system_table_mods, bool is_internal, + Oid relam, ObjectAddress *typaddress); extern void heap_create_init_fork(Relation rel); diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index c38958d..6a8eeb4 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -60,8 +60,8 @@ extern void CatalogUpdateIndexes(Relation heapRel, HeapTuple heapTuple); DECLARE_UNIQUE_INDEX(pg_aggregate_fnoid_index, 2650, on pg_aggregate using btree(aggfnoid oid_ops)); #define AggregateFnoidIndexId 2650 -DECLARE_UNIQUE_INDEX(pg_am_name_index, 2651, on pg_am using btree(amname name_ops)); -#define AmNameIndexId 2651 +DECLARE_UNIQUE_INDEX(pg_am_name_kind_index, 2651, on pg_am using btree(amname name_ops, amkind char_ops)); +#define AmNameKindIndexId 2651 DECLARE_UNIQUE_INDEX(pg_am_oid_index, 2652, on pg_am using btree(oid oid_ops)); #define AmOidIndexId 2652 diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h index f19a873..ecc8812 100644 --- a/src/include/catalog/pg_am.h +++ b/src/include/catalog/pg_am.h @@ -34,6 +34,7 @@ CATALOG(pg_am,2601) { NameData amname; /* access method name */ + char amkind; /* access method kind */ regproc amhandler; /* handler function */ } FormData_pg_am; @@ -48,32 +49,40 @@ typedef FormData_pg_am *Form_pg_am; * compiler constants for pg_am * ---------------- */ -#define Natts_pg_am 2 +#define Natts_pg_am 3 #define Anum_pg_am_amname 1 -#define Anum_pg_am_amhandler 2 +#define Anum_pg_am_amkind 2 +#define Anum_pg_am_amhandler 3 /* ---------------- * initial contents of pg_am * ---------------- */ -DATA(insert OID = 403 ( btree bthandler )); +DATA(insert OID = 403 ( btree i bthandler )); DESCR("b-tree index access method"); #define BTREE_AM_OID 403 -DATA(insert OID = 405 ( hash hashhandler )); +DATA(insert OID = 405 ( hash i hashhandler )); DESCR("hash index access method"); #define HASH_AM_OID 405 -DATA(insert OID = 783 ( gist gisthandler )); +DATA(insert OID = 783 ( gist i gisthandler )); DESCR("GiST index access method"); #define GIST_AM_OID 783 -DATA(insert OID = 2742 ( gin ginhandler )); +DATA(insert OID = 2742 ( gin i ginhandler )); DESCR("GIN index access method"); #define GIN_AM_OID 2742 -DATA(insert OID = 4000 ( spgist spghandler )); +DATA(insert OID = 4000 ( spgist i spghandler )); DESCR("SP-GiST index access method"); #define SPGIST_AM_OID 4000 -DATA(insert OID = 3580 ( brin brinhandler )); +DATA(insert OID = 3580 ( brin i brinhandler )); DESCR("block range index (BRIN) access method"); #define BRIN_AM_OID 3580 +DATA(insert OID = 6022 ( local S seqam_local_handler )); +DESCR("local sequence access method"); +#define LOCAL_SEQAM_OID 6022 + +#define AMKIND_INDEX 'i' /* index */ +#define AMKIND_SEQUENCE 'S' /* sequence */ + #endif /* PG_AM_H */ diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index b3c6915..57bfbfb 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -1735,6 +1735,10 @@ DATA(insert OID = 1765 ( setval PGNSP PGUID 12 1 0 0 0 f f f f t f v u 3 0 20 DESCR("set sequence value and is_called status"); DATA(insert OID = 3078 ( pg_sequence_parameters PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 2249 "26" "{26,20,20,20,20,16}" "{i,o,o,o,o,o}" "{sequence_oid,start_value,minimum_value,maximum_value,increment,cycle_option}" _null_ _null_ pg_sequence_parameters _null_ _null_ _null_)); DESCR("sequence parameters, for use by information schema"); +DATA(insert OID = 3377 ( pg_sequence_get_state PGNSP PGUID 12 1 0 0 0 f f f f t f v u 1 0 25 "2205" _null_ _null_ _null_ _null_ _null_ pg_sequence_get_state _null_ _null_ _null_ )); +DESCR("Dump state of a sequence"); +DATA(insert OID = 3378 ( pg_sequence_set_state PGNSP PGUID 12 1 0 0 0 f f f f t f v u 2 0 2278 "2205 25" _null_ _null_ _null_ _null_ _null_ pg_sequence_set_state _null_ _null_ _null_ )); +DESCR("Restore state of a sequence"); DATA(insert OID = 1579 ( varbit_in PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 1562 "2275 26 23" _null_ _null_ _null_ _null_ _null_ varbit_in _null_ _null_ _null_ )); DESCR("I/O"); @@ -3636,6 +3640,10 @@ DESCR("BERNOULLI tablesample method handler"); DATA(insert OID = 3314 ( system PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 3310 "2281" _null_ _null_ _null_ _null_ _null_ tsm_system_handler _null_ _null_ _null_ )); DESCR("SYSTEM tablesample method handler"); +/* sequence access method handlers */ +DATA(insert OID = 6023 ( seqam_local_handler PGNSP PGUID 12 1 0 0 0 f f f f f f v s 2 0 17 "2281 16" _null_ _null_ _null_ _null_ _null_ seqam_local_handler _null_ _null_ _null_ )); +DESCR("Local SequenceAM handler"); + /* cryptographic */ DATA(insert OID = 2311 ( md5 PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 25 "25" _null_ _null_ _null_ _null_ _null_ md5_text _null_ _null_ _null_ )); DESCR("MD5 hash"); @@ -5181,6 +5189,11 @@ DESCR("brin handler"); DATA(insert OID = 336 ( amvalidate PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 16 "26" _null_ _null_ _null_ _null_ _null_ amvalidate _null_ _null_ _null_ )); DESCR("ask access method to validate opclass"); +/* Sequence AM */ +DATA(insert OID = 6027 ( seqam_local_state_in PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 6025 "2275" _null_ _null_ _null_ _null_ _null_ seqam_local_state_in _null_ _null_ _null_ )); +DESCR("I/O"); +DATA(insert OID = 6028 ( seqam_local_state_out PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2275 "6025" _null_ _null_ _null_ _null_ _null_ seqam_local_state_out _null_ _null_ _null_ )); +DESCR("I/O"); /* diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h index 59b04ac..9900c3d 100644 --- a/src/include/catalog/pg_type.h +++ b/src/include/catalog/pg_type.h @@ -701,6 +701,11 @@ DATA(insert OID = 325 ( index_am_handler PGNSP PGUID 4 t p P f t \054 0 0 0 ind DATA(insert OID = 3831 ( anyrange PGNSP PGUID -1 f p P f t \054 0 0 0 anyrange_in anyrange_out - - - - - d x f 0 -1 0 0 _null_ _null_ _null_ )); #define ANYRANGEOID 3831 +/* Sequence AM composite types */ +DATA(insert OID = 6025 ( seqam_local_state PGNSP PGUID 16 f b U f t \054 0 0 6026 seqam_local_state_in seqam_local_state_out - - - - - c p f 0 -1 0 0 _null_ _null_ _null_ )); +#define SEQAMLOCALSTATEOID 6025 +DATA(insert OID = 6026 ( _seqam_local_state PGNSP PGUID -1 f b A f t \054 0 6025 0 array_in array_out array_recv array_send - - array_typanalyze i x f 0 -1 0 0 _null_ _null_ _null_ )); + /* * macros diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 112f9dc..0493d8a 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -91,7 +91,7 @@ extern void IsThereOpClassInNamespace(const char *opcname, Oid opcmethod, Oid opcnamespace); extern void IsThereOpFamilyInNamespace(const char *opfname, Oid opfmethod, Oid opfnamespace); -extern Oid get_am_oid(const char *amname, bool missing_ok); +extern Oid get_index_am_oid(const char *amname, bool missing_ok); extern char *get_am_name(Oid amOid); extern Oid get_opclass_oid(Oid amID, List *opclassname, bool missing_ok); extern Oid get_opfamily_oid(Oid amID, List *opfamilyname, bool missing_ok); diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h index 44862bb..7841527 100644 --- a/src/include/commands/sequence.h +++ b/src/include/commands/sequence.h @@ -19,20 +19,18 @@ #include "lib/stringinfo.h" #include "nodes/parsenodes.h" #include "storage/relfilenode.h" - +#include "access/htup_details.h" typedef struct FormData_pg_sequence { - NameData sequence_name; - int64 last_value; int64 start_value; int64 increment_by; int64 max_value; int64 min_value; int64 cache_value; - int64 log_cnt; bool is_cycled; - bool is_called; + /* amstate follows */ + char amstate[FLEXIBLE_ARRAY_MEMBER]; } FormData_pg_sequence; typedef FormData_pg_sequence *Form_pg_sequence; @@ -41,19 +39,16 @@ typedef FormData_pg_sequence *Form_pg_sequence; * Columns of a sequence relation */ -#define SEQ_COL_NAME 1 -#define SEQ_COL_LASTVAL 2 -#define SEQ_COL_STARTVAL 3 -#define SEQ_COL_INCBY 4 -#define SEQ_COL_MAXVALUE 5 -#define SEQ_COL_MINVALUE 6 -#define SEQ_COL_CACHE 7 -#define SEQ_COL_LOG 8 -#define SEQ_COL_CYCLE 9 -#define SEQ_COL_CALLED 10 +#define SEQ_COL_STARTVAL 1 +#define SEQ_COL_INCBY 2 +#define SEQ_COL_MAXVALUE 3 +#define SEQ_COL_MINVALUE 4 +#define SEQ_COL_CACHE 5 +#define SEQ_COL_CYCLE 6 +#define SEQ_COL_AMSTATE 7 -#define SEQ_COL_FIRSTCOL SEQ_COL_NAME -#define SEQ_COL_LASTCOL SEQ_COL_CALLED +#define SEQ_COL_FIRSTCOL SEQ_COL_STARTVAL +#define SEQ_COL_LASTCOL SEQ_COL_AMSTATE /* XLOG stuff */ #define XLOG_SEQ_LOG 0x00 @@ -72,6 +67,8 @@ extern Datum setval3_oid(PG_FUNCTION_ARGS); extern Datum lastval(PG_FUNCTION_ARGS); extern Datum pg_sequence_parameters(PG_FUNCTION_ARGS); +extern Datum pg_sequence_get_state(PG_FUNCTION_ARGS); +extern Datum pg_sequence_set_state(PG_FUNCTION_ARGS); extern ObjectAddress DefineSequence(CreateSeqStmt *stmt); extern ObjectAddress AlterSequence(AlterSeqStmt *stmt); diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index f269c63..cb621b1 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -23,7 +23,7 @@ extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, - ObjectAddress *typaddress); + Oid relamid, ObjectAddress *typaddress); extern void RemoveRelations(DropStmt *drop); diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 0ad9ee9..58d0dce 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -457,7 +457,8 @@ typedef enum NodeTag T_InlineCodeBlock, /* in nodes/parsenodes.h */ T_FdwRoutine, /* in foreign/fdwapi.h */ T_TsmRoutine, /* in access/tsmapi.h */ - T_IndexAmRoutine /* in access/amapi.h */ + T_IndexAmRoutine, /* in access/amapi.h */ + T_SequenceAM /* in access/seqam.h */ } NodeTag; /* diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index abd4dd1..cd5db91 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2192,8 +2192,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; @@ -2201,8 +2203,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/rel.h b/src/include/utils/rel.h index 483b840..a8441d1 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -124,6 +124,9 @@ typedef struct RelationData */ bytea *rd_options; /* parsed pg_class.reloptions */ + /* These are non-NULL only for a sequence relation */ + struct SequenceAM *rd_seqam; + /* These are non-NULL only for an index relation: */ Form_pg_index rd_index; /* pg_index tuple describing this index */ /* use "struct" here to avoid needing to include htup.h: */ @@ -132,7 +135,7 @@ typedef struct RelationData IndexAmRoutine *amroutine; /* - * index access support info (used only for an index relation) + * index access support info (used only for index relations) * * Note: only default support procs for each opclass are cached, namely * those with lefttype and righttype equal to the opclass's opcintype. The diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h index 18404e2..7a666a5 100644 --- a/src/include/utils/syscache.h +++ b/src/include/utils/syscache.h @@ -32,7 +32,7 @@ enum SysCacheIdentifier { AGGFNOID = 0, - AMNAME, + AMNAMEKIND, AMOID, AMOPOPID, AMOPSTRATEGY, diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out index ae50c94..f1b1fc2 100644 --- a/src/test/regress/expected/create_view.out +++ b/src/test/regress/expected/create_view.out @@ -129,8 +129,9 @@ NOTICE: view "v12_temp" will be a temporary view -- a view should also be temporary if it references a temporary sequence CREATE SEQUENCE seq1; CREATE TEMPORARY SEQUENCE seq1_temp; -CREATE VIEW v9 AS SELECT seq1.is_called FROM seq1; -CREATE VIEW v13_temp AS SELECT seq1_temp.is_called FROM seq1_temp; +CREATE TYPE seqam_local_state_rec AS (last_value int8, is_called bool, log_cnt int4); +CREATE VIEW v9 AS SELECT (seq1::text::seqam_local_state_rec).is_called FROM seq1; +CREATE VIEW v13_temp AS SELECT (seq1_temp::text::seqam_local_state_rec).is_called FROM seq1_temp; NOTICE: view "v13_temp" will be a temporary view SELECT relname FROM pg_class WHERE relname LIKE 'v_' diff --git a/src/test/regress/expected/sequence.out b/src/test/regress/expected/sequence.out index 8783ca6..3a19cde 100644 --- a/src/test/regress/expected/sequence.out +++ b/src/test/regress/expected/sequence.out @@ -129,10 +129,10 @@ SELECT nextval('sequence_test'::regclass); 33 (1 row) -SELECT setval('sequence_test'::text, 99, false); - setval --------- - 99 +SELECT pg_sequence_set_state('sequence_test'::text, '(99,false)'); + pg_sequence_set_state +----------------------- + (1 row) SELECT nextval('sequence_test'::regclass); @@ -153,10 +153,10 @@ SELECT nextval('sequence_test'::text); 33 (1 row) -SELECT setval('sequence_test'::regclass, 99, false); - setval --------- - 99 +SELECT pg_sequence_set_state('sequence_test'::regclass, '(99,false)'); + pg_sequence_set_state +----------------------- + (1 row) SELECT nextval('sequence_test'::text); @@ -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 + start_value | increment_by | max_value | min_value | cache_value | is_cycled | amstate +-------------+--------------+---------------------+-----------+-------------+-----------+--------- + 1 | 1 | 9223372036854775807 | 1 | 1 | f | (1,f,0) (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 + start_value | increment_by | max_value | min_value | cache_value | is_cycled | amstate +-------------+--------------+---------------------+-----------+-------------+-----------+---------- + 1 | 1 | 9223372036854775807 | 1 | 1 | f | (2,t,31) (1 row) DROP SEQUENCE foo_seq_new; @@ -300,6 +300,13 @@ SELECT nextval('sequence_test2'); 5 (1 row) +-- Sequence Acess Method +CREATE SEQUENCE myamseq USING local WITH (foo = 'bar'); +ERROR: local sequence does not accept any storage parameters +CREATE SEQUENCE myamseq USING local; +ALTER SEQUENCE myamseq SET (foo = 'baz'); +ERROR: local sequence does not accept any storage parameters +DROP SEQUENCE myamseq; -- Information schema SELECT * FROM information_schema.sequences WHERE sequence_name IN ('sequence_test2', 'serialtest2_f2_seq', 'serialtest2_f3_seq', diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out index 6c71371..97b12fb 100644 --- a/src/test/regress/expected/updatable_views.out +++ b/src/test/regress/expected/updatable_views.out @@ -86,55 +86,52 @@ SELECT table_name, column_name, is_updatable FROM information_schema.columns WHERE table_name LIKE E'r_\\_view%' ORDER BY table_name, ordinal_position; - table_name | column_name | is_updatable -------------+---------------+-------------- - ro_view1 | a | NO - ro_view1 | b | NO - ro_view10 | a | NO - ro_view11 | a | NO - ro_view11 | b | NO - ro_view12 | a | NO - ro_view13 | a | NO - ro_view13 | b | NO - ro_view17 | a | NO - ro_view17 | b | NO - ro_view18 | a | NO - ro_view19 | sequence_name | NO - ro_view19 | last_value | NO - ro_view19 | start_value | NO - ro_view19 | increment_by | NO - ro_view19 | max_value | NO - ro_view19 | min_value | NO - ro_view19 | cache_value | NO - ro_view19 | log_cnt | NO - ro_view19 | is_cycled | NO - ro_view19 | is_called | NO - ro_view2 | a | NO - ro_view2 | b | NO - ro_view20 | a | NO - ro_view20 | b | NO - ro_view20 | g | NO - ro_view3 | ?column? | NO - ro_view4 | count | NO - ro_view5 | a | NO - ro_view5 | rank | NO - ro_view6 | a | NO - ro_view6 | b | NO - ro_view7 | a | NO - ro_view7 | b | NO - ro_view8 | a | NO - ro_view8 | b | NO - ro_view9 | a | NO - ro_view9 | b | NO - rw_view14 | ctid | NO - rw_view14 | a | YES - rw_view14 | b | YES - rw_view15 | a | YES - rw_view15 | upper | NO - rw_view16 | a | YES - rw_view16 | b | YES - rw_view16 | aa | YES -(46 rows) + table_name | column_name | is_updatable +------------+--------------+-------------- + ro_view1 | a | NO + ro_view1 | b | NO + ro_view10 | a | NO + ro_view11 | a | NO + ro_view11 | b | NO + ro_view12 | a | NO + ro_view13 | a | NO + ro_view13 | b | NO + ro_view17 | a | NO + ro_view17 | b | NO + ro_view18 | a | NO + ro_view19 | start_value | NO + ro_view19 | increment_by | NO + ro_view19 | max_value | NO + ro_view19 | min_value | NO + ro_view19 | cache_value | NO + ro_view19 | is_cycled | NO + ro_view19 | amstate | NO + ro_view2 | a | NO + ro_view2 | b | NO + ro_view20 | a | NO + ro_view20 | b | NO + ro_view20 | g | NO + ro_view3 | ?column? | NO + ro_view4 | count | NO + ro_view5 | a | NO + ro_view5 | rank | NO + ro_view6 | a | NO + ro_view6 | b | NO + ro_view7 | a | NO + ro_view7 | b | NO + ro_view8 | a | NO + ro_view8 | b | NO + ro_view9 | a | NO + ro_view9 | b | NO + rw_view14 | ctid | NO + rw_view14 | a | YES + rw_view14 | b | YES + rw_view15 | a | YES + rw_view15 | upper | NO + rw_view16 | a | YES + rw_view16 | b | YES + rw_view16 | aa | YES +(43 rows) -- Read-only views DELETE FROM ro_view1; diff --git a/src/test/regress/sql/create_view.sql b/src/test/regress/sql/create_view.sql index 58d361d..e967fff 100644 --- a/src/test/regress/sql/create_view.sql +++ b/src/test/regress/sql/create_view.sql @@ -131,8 +131,9 @@ CREATE VIEW v12_temp AS SELECT true FROM v11_temp; -- a view should also be temporary if it references a temporary sequence CREATE SEQUENCE seq1; CREATE TEMPORARY SEQUENCE seq1_temp; -CREATE VIEW v9 AS SELECT seq1.is_called FROM seq1; -CREATE VIEW v13_temp AS SELECT seq1_temp.is_called FROM seq1_temp; +CREATE TYPE seqam_local_state_rec AS (last_value int8, is_called bool, log_cnt int4); +CREATE VIEW v9 AS SELECT (seq1::text::seqam_local_state_rec).is_called FROM seq1; +CREATE VIEW v13_temp AS SELECT (seq1_temp::text::seqam_local_state_rec).is_called FROM seq1_temp; SELECT relname FROM pg_class WHERE relname LIKE 'v_' diff --git a/src/test/regress/sql/sequence.sql b/src/test/regress/sql/sequence.sql index 0dd653d..c2b1164 100644 --- a/src/test/regress/sql/sequence.sql +++ b/src/test/regress/sql/sequence.sql @@ -67,11 +67,11 @@ SELECT currval('sequence_test'::text); SELECT currval('sequence_test'::regclass); SELECT setval('sequence_test'::text, 32); SELECT nextval('sequence_test'::regclass); -SELECT setval('sequence_test'::text, 99, false); +SELECT pg_sequence_set_state('sequence_test'::text, '(99,false)'); SELECT nextval('sequence_test'::regclass); SELECT setval('sequence_test'::regclass, 32); SELECT nextval('sequence_test'::text); -SELECT setval('sequence_test'::regclass, 99, false); +SELECT pg_sequence_set_state('sequence_test'::regclass, '(99,false)'); SELECT nextval('sequence_test'::text); DISCARD SEQUENCES; SELECT currval('sequence_test'::regclass); @@ -138,6 +138,12 @@ SELECT nextval('sequence_test2'); SELECT nextval('sequence_test2'); SELECT nextval('sequence_test2'); +-- Sequence Acess Method +CREATE SEQUENCE myamseq USING local WITH (foo = 'bar'); +CREATE SEQUENCE myamseq USING local; +ALTER SEQUENCE myamseq SET (foo = 'baz'); +DROP SEQUENCE myamseq; + -- Information schema SELECT * FROM information_schema.sequences WHERE sequence_name IN ('sequence_test2', 'serialtest2_f2_seq', 'serialtest2_f3_seq', -- 1.9.1