From 09ed03975d8345c052387866cafef47ad2bc060b Mon Sep 17 00:00:00 2001 From: Petr Jelinek Date: Wed, 2 Mar 2016 16:15:33 +0100 Subject: [PATCH 1/2] seqam --- doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/postgres.sgml | 1 + doc/src/sgml/ref/alter_sequence.sgml | 55 + doc/src/sgml/ref/create_access_method.sgml | 9 +- doc/src/sgml/ref/create_sequence.sgml | 25 + doc/src/sgml/seqam.sgml | 312 +++++ src/backend/access/Makefile | 4 +- src/backend/access/common/reloptions.c | 13 +- src/backend/access/sequence/Makefile | 17 + src/backend/access/sequence/seqamapi.c | 191 ++++ src/backend/access/sequence/seqlocal.c | 408 +++++++ src/backend/bootstrap/bootparse.y | 1 + src/backend/catalog/heap.c | 17 +- src/backend/catalog/objectaddress.c | 1 + src/backend/catalog/toasting.c | 1 + src/backend/commands/amcmds.c | 38 +- src/backend/commands/cluster.c | 1 + src/backend/commands/createas.c | 3 +- src/backend/commands/indexcmds.c | 2 +- src/backend/commands/sequence.c | 1502 +++++++++++++++---------- src/backend/commands/tablecmds.c | 49 +- 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 | 91 +- src/backend/parser/parse_utilcmd.c | 3 + src/backend/tcop/utility.c | 6 +- src/backend/utils/adt/pseudotypes.c | 25 + src/backend/utils/cache/relcache.c | 53 +- src/backend/utils/misc/guc.c | 1 + src/bin/pg_dump/pg_dump.c | 117 +- src/bin/pg_dump/pg_dump.h | 1 + src/bin/psql/describe.c | 112 +- src/include/access/reloptions.h | 2 +- src/include/access/seqamapi.h | 109 ++ src/include/catalog/heap.h | 1 + src/include/catalog/pg_am.h | 5 + src/include/catalog/pg_proc.h | 19 + src/include/catalog/pg_type.h | 7 + src/include/commands/defrem.h | 1 + src/include/commands/sequence.h | 31 +- src/include/commands/tablecmds.h | 2 +- src/include/nodes/nodes.h | 1 + src/include/nodes/parsenodes.h | 8 +- src/include/utils/builtins.h | 2 + src/include/utils/rel.h | 8 +- src/test/regress/expected/create_am.out | 19 + src/test/regress/expected/create_view.out | 5 +- src/test/regress/expected/opr_sanity.out | 4 +- src/test/regress/expected/sequence.out | 35 +- src/test/regress/expected/updatable_views.out | 95 +- src/test/regress/sql/create_am.sql | 16 + src/test/regress/sql/create_view.sql | 5 +- src/test/regress/sql/opr_sanity.sql | 4 +- src/test/regress/sql/sequence.sql | 10 +- 56 files changed, 2699 insertions(+), 764 deletions(-) create mode 100644 doc/src/sgml/seqam.sgml create mode 100644 src/backend/access/sequence/Makefile create mode 100644 src/backend/access/sequence/seqamapi.c create mode 100644 src/backend/access/sequence/seqlocal.c create mode 100644 src/include/access/seqamapi.h diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 30adece..fd355dc 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -90,6 +90,7 @@ + diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml index 7e82cdc..d37058a 100644 --- a/doc/src/sgml/postgres.sgml +++ b/doc/src/sgml/postgres.sgml @@ -251,6 +251,7 @@ &spgist; &gin; &brin; + &seqam; &storage; &bki; &planstats; 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_access_method.sgml b/doc/src/sgml/ref/create_access_method.sgml index 3c091f8..8676a84 100644 --- a/doc/src/sgml/ref/create_access_method.sgml +++ b/doc/src/sgml/ref/create_access_method.sgml @@ -61,7 +61,8 @@ CREATE ACCESS METHOD name This clause specifies type of access method to define. - Only INDEX is supported at present. + Supported values are INDEX for index access methods + and SEQUENCE for sequence access methods. @@ -75,7 +76,9 @@ CREATE ACCESS METHOD name of access method to the core. The handler function must take single argument of type internal, and its return type depends on the type of access method; for INDEX access methods, it - must be index_am_handler. + must be index_am_handler and for + SEQUENCE access methods it must be + sequence_am_handler. @@ -114,6 +117,8 @@ CREATE ACCESS METHOD heptree TYPE INDEX HANDLER heptree_handler; + + 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/doc/src/sgml/seqam.sgml b/doc/src/sgml/seqam.sgml new file mode 100644 index 0000000..5faeae3 --- /dev/null +++ b/doc/src/sgml/seqam.sgml @@ -0,0 +1,312 @@ + + + + Sequence Access Method Interface Definition + + + This chapter describes the interface for writing sequence access methods. + + + + The sequence access method defines behavior of a sequence which is using it. + Mainly how the new values are generated. Each sequence can have different + access method in order to behave in a way that makes most sense in the + application context. + + + + Catalog Entries for Sequence Access Method + + + Each sequence access method is described by a row in the + pg_am system catalog (see + ). + + + + + Sequence Access Method Functions + + + The SQL interface of the sequence access method consists of a handler + function which has to return special pseudo-type + sequence_handler. This function must be written in a compiled + language such as C, using the version-1 interface. For details on C + language calling conventions and dynamic loading, see + . + + + + The actual C structure returned by the handler function is following: + +typedef struct SeqAmRoutine +{ + NodeTag type; + + /* Custom datatype used for storing state of the sequence by the AM */ + Oid StateTypeOid; + + /* Function for parsing reloptions */ + ParseRelOptions_function amoptions; + + /* Initialization */ + SeqAMInit_function Init; + + /* nextval handler */ + SeqAMAlloc_function Alloc; + + /* State manipulation functions */ + SeqAMSetval_function Setval; + SeqAMGetState_function GetState; + SeqAMSetState_function SetState; +} SeqAmRoutine; + + + + + These are the functions implemented by the sequence access method. + + + + The functions defining a sequence access method are: + + + + +Datum +SeqAMInit (Relation seqrel, Form_pg_sequence seq, int64 restart_value, + bool restart_requested, bool is_init) + + Initialize the sequence's internal state. This function is called both + when a new sequence is created, when the ALTER SEQUENCE command + is called for an existing sequence or by the + TRUNCATE RESTART IDENTITY command. + + + + The seqrel points to relcache record representing the + pg_class + tuple tuple of the sequence. + The seq is in-memory version of the + pg_sequence tuple itself. The + restart_value is value the sequence should start from and the + restart_requested gives indication if the + restart_value should indeed be used. Finally is_init + is true when this is new sequence being created or when the + access method of a sequence has been changed, otherwise it's + false. + + to the options in the CREATE SEQUENCE or + ALTER SEQUENCE statement. + reloptions contains parsed reloptions passed to the + CREATE SEQUENCE or ALTER SEQUENCE statements. + The values and nulls describe new tuple for the + pg_sequence tuple which this function can update + as needed. + + + + This function should return the Datum of a state of the sequence. The type + of the Datum is the StateTypeOid type. + + + + +bytea * +ParseRelOptions (Datum reloptions, bool validate) + + Parse and validate the reloptions array for an sequence. + reloptions is a text array containing entries of the + form name=value. + The function should construct a bytea value. + When validate is true, the function should report a suitable + error message if any of the options are unrecognized or have invalid + values; when validate is false, invalid entries should be + silently ignored. (validate is false when loading options + already stored in pg_catalog; an invalid entry could only + be found if the access method has changed its rules for options, and in + that case ignoring obsolete entries is appropriate.) + It is OK to return NULL if default behavior is wanted. + + + + +int64 +SeqAMAlloc (Relation seqrel, SequenceHandle *seqh, int64 nrequested, + int64 *last) + + Allocate new sequence value(s). The nrequested specifies how + many new values should be allocated by this call. Return value is the next + allocated value and last should be set to last allocated value. + + + + +void SeqAMSetval (Relation seqrel, SequenceHandle *seqh, int64 new_value) + + Set the current sequence value the the new_value. + + + + +Datum SeqAMGetState (Relation seqrel, SequenceHandle *seqh) + + Dump the current state of the sequence. The return value is the Datum of + a StateTypeOid. This interface is mainly + used by pg_dump. + + + + +void SeqAMSetState (Relation seqrel, SequenceHandle *seqh, Datum amstate) + + Restore state of the sequence based on amstate which is Datum of a + StateTypeOid type. + This interface is mainly used by pg_dump. + + + + + + Sequence Access Method Storage API + + + To store the current state of the sequence, the backend provides the + following helper functions which the sequence access method can use: + + + + +void +sequence_open(Oid seqrelid, SequenceHandle *seqh) + + Open sequence with given relid. The seqh is + output parameter which will be set to the sequence handle. + + + + +void +sequence_close (SequenceHandle *seqh) + + Release and close the opened sequence. + + + + +Form_pg_sequence +(SequenceHandle *seqh) + + Reads and locks the sequence tuple for processing by the access method. + Returns the whole pg_sequence tuple as the + Form_pg_sequence structure. + The tuple should be released by calling sequence_release_tuple. + + + + +Datum +sequence_read_state(SequenceHandle *seqh) + + Reads and locks the sequence tuple for processing by the access method. + Returns just the sequence access method specific state Datum. + The tuple should be released by calling sequence_release_tuple. + + + + +void +sequence_start_update(SequenceHandle *seqh, bool do_wal) + + Start the sequence update. The do_wal gives hint if at least + one of subsequent sequence_apply_update() calls will be with + do_wal = true. + + + + +void +sequence_save_state(SequenceHandle *seqh, Datum seqstate, bool dowal) + + Save the modified sequence state tuple indicating if the change should be + WAL logged as well as saved to the catalog. + + + + +void +sequence_finish_update(SequenceHandle *seqh) + + Finish the sequence update. + + + + To update the sequence tuple you have to call all three above functions in + order to be sure the update is applied in error safe manner. The + sequence_save_update() can be called multiple times between + single pair of sequence_start_update() and + sequence_finish_update() calls. Update function cannot be called + if the tuple was already released by the + sequence_release_tuple() function. + + + + +void +sequence_release_tuple(SequenceHandle *seqh) + + Release the tuple read and locked by sequence_read_options + and/or added by sequence_read_state. + + + + + + Sequence Access Method Utility Functions + + + Additional utility functions which can be useful when writing sequence + access methods: + + + + +bool +sequence_needs_wal(SequenceHandle *seqh) + + Returns true if the sequence tuple was last saved before last + checkpoint. This can be help when deciding what to set do_wal + to when calling sequence_save_update. + + + + +int64 +sequence_increment(Relation seqrel, int64 *value, int64 incnum, + int64 minv, int64 maxv, int64 incby, + bool is_cycled, bool report_errors) + + Helper function to increment value of a sequence, correctly handling + sequence options like min value, max value, increment value and cycling of + the sequence value. The value is pointer to current value which + should be incremented, incnum specifies how much should the + value be incremented, the rest of the options should be set to values + specified by the sequence's pg_sequence tuple. + Returns by how much was the value increased. + + + + +void +sequence_check_range (int64 value, int64 min_value, + int64 max_value, const char *valname) + + Checks if the value is between min_value + and max_value and raises standard error message if it's not. + The valname is used as the name of the wrong value in the error + message. + + + + + 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 ea0755a..bb368d2 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -888,8 +888,8 @@ untransformRelOptions(Datum options) * instead. * * tupdesc is pg_class' tuple descriptor. amoptions is a pointer to the index - * AM's options parser function in the case of a tuple corresponding to an - * index, or NULL otherwise. + * or sequence AM's options parser function in the case of a tuple corresponding + * to an index or sequence respectively, or NULL otherwise. */ bytea * extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, @@ -921,7 +921,8 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, 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; @@ -1374,14 +1375,14 @@ heap_reloptions(char relkind, Datum reloptions, bool validate) /* - * Parse options for indexes. + * Parse options for access methods. * - * amoptions index AM's option parser function + * amoptions AM's option parser function * 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 != NULL); diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile new file mode 100644 index 0000000..1784799 --- /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 = seqamapi.o seqlocal.o + +include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/seqamapi.c b/src/backend/access/sequence/seqamapi.c new file mode 100644 index 0000000..bfdd589 --- /dev/null +++ b/src/backend/access/sequence/seqamapi.c @@ -0,0 +1,191 @@ +/*------------------------------------------------------------------------- + * + * seqamapi.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/seqamapi.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/seqamapi.h" +#include "access/transam.h" +#include "access/xact.h" +#include "catalog/pg_am.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" + + +/* + * GetSeqAmRoutine - call the specified access method handler routine + * to get its SeqAmRoutine struct. + */ +SeqAmRoutine * +GetSeqAmRoutine(Oid amhandler) +{ + Datum datum; + SeqAmRoutine *routine; + + datum = OidFunctionCall0(amhandler); + routine = (SeqAmRoutine *) DatumGetPointer(datum); + + if (routine == NULL || !IsA(routine, SeqAmRoutine)) + elog(ERROR, "sequence access method handler function %u did not return an SeqAmRoutine struct", + amhandler); + + return routine; +} + +/* + * GetSeqAmRoutineByAMId - look up the handler of the sequence access method + * for the given OID, and retrieve its SeqAmRoutine struct. + */ +SeqAmRoutine * +GetSeqAmRoutineByAMId(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 GetSeqAmRoutine(amhandler); +} + +/* + * GetSeqAmRoutineForRelation - look up the handler of the sequence access + * method for the given sequence Relation. + */ +SeqAmRoutine * +GetSeqAmRoutineForRelation(Relation relation) +{ + SeqAmRoutine *seqam; + SeqAmRoutine *cseqam; + + if (relation->rd_seqamroutine == 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 = GetSeqAmRoutineByAMId(relation->rd_rel->relam); + /* Save the data for later reuse in CacheMemoryContext */ + cseqam = (SeqAmRoutine *) MemoryContextAlloc(CacheMemoryContext, + sizeof(SeqAmRoutine)); + memcpy(cseqam, seqam, sizeof(SeqAmRoutine)); + relation->rd_seqamroutine = cseqam; + } + + return relation->rd_seqamroutine; +} + +/* + * ValidateSeqAmRoutine - to sanity check for the SeqAmRoutine returned by a + * handler. + * + * At the moment we only check that the StateTypeOid is valid type oid and that + * the type it points to is fixed width and will fit into the sequence page. + */ +void +ValidateSeqAmRoutine(SeqAmRoutine *routine) +{ +#define MAX_SEQAM_STATE_LEN \ + MaxHeapTupleSize - MinHeapTupleSize - MAXALIGN(offsetof(FormData_pg_sequence, amstate)) + + Oid stattypeoid = routine->StateTypeOid; + + if (!get_typisdefined(stattypeoid)) + ereport(ERROR, + (errmsg("type with OID %u does not exist", + stattypeoid))); + + if (TypeIsToastable(stattypeoid)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("sequence access method state data type \"%s\" must not be toastable", + format_type_be(stattypeoid)))); + + if (get_typlen(stattypeoid) >= MAX_SEQAM_STATE_LEN) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("sequence access method state data type \"%s\" exceeds maximum allowed length (%lu)", + format_type_be(stattypeoid), MAX_SEQAM_STATE_LEN))); + +} diff --git a/src/backend/access/sequence/seqlocal.c b/src/backend/access/sequence/seqlocal.c new file mode 100644 index 0000000..2d31478 --- /dev/null +++ b/src/backend/access/sequence/seqlocal.c @@ -0,0 +1,408 @@ +/*------------------------------------------------------------------------- + * + * 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/seqamapi.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); +} + +/* + * Handler function for the local sequence access method. + */ +Datum +seqam_local_handler(PG_FUNCTION_ARGS) +{ + SeqAmRoutine *seqam = makeNode(SeqAmRoutine); + + seqam->StateTypeOid = SEQAMLOCALSTATEOID; + seqam->amoptions = 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) +{ + Form_pg_sequence seq; + LocalSequenceState *localseq; + + seq = sequence_read_options(seqh); + localseq = (LocalSequenceState *) DatumGetPointer(amstate); + + sequence_check_range(localseq->last_value, seq->min_value, seq->max_value, + "last_value"); + + 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 41d2fd4..e684f60 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 e997b57..b7cd40a 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -41,6 +41,7 @@ #include "catalog/heap.h" #include "catalog/index.h" #include "catalog/objectaccess.h" +#include "catalog/pg_am.h" #include "catalog/pg_attrdef.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" @@ -88,6 +89,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, @@ -849,6 +851,7 @@ AddNewRelationTuple(Relation pg_class_desc, Oid reloftype, Oid relowner, char relkind, + Oid relam, Datum relacl, Datum reloptions) { @@ -922,6 +925,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; @@ -1035,6 +1039,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; @@ -1263,6 +1268,7 @@ heap_create_with_catalog(const char *relname, reloftypeid, ownerid, relkind, + relam, PointerGetDatum(relacl), reloptions); @@ -1275,7 +1281,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 pg_am record. * * For composite types, these dependencies are tracked for the pg_type * entry, so we needn't record them here. Likewise, TOAST tables don't @@ -1330,6 +1337,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 cb3ba85..b40f0c7 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/seqamapi.h" #include "access/sysattr.h" #include "catalog/catalog.h" #include "catalog/indexing.h" diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c index f40a005..40b5e91 100644 --- a/src/backend/catalog/toasting.c +++ b/src/backend/catalog/toasting.c @@ -291,6 +291,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/amcmds.c b/src/backend/commands/amcmds.c index 7a93754..58261d4 100644 --- a/src/backend/commands/amcmds.c +++ b/src/backend/commands/amcmds.c @@ -15,6 +15,8 @@ #include "access/heapam.h" #include "access/htup_details.h" +#include "access/seqamapi.h" +#include "access/xact.h" #include "catalog/dependency.h" #include "catalog/indexing.h" #include "catalog/pg_am.h" @@ -29,7 +31,7 @@ #include "utils/syscache.h" -static Oid lookup_index_am_handler_func(List *handler_name, char amtype); +static Oid lookup_am_handler_func(List *handler_name, char amtype); static char *get_am_type_string(char amtype); @@ -72,7 +74,7 @@ CreateAccessMethod(CreateAmStmt *stmt) /* * Get the handler function oid, verifying the AM type while at it. */ - amhandler = lookup_index_am_handler_func(stmt->handler_name, stmt->amtype); + amhandler = lookup_am_handler_func(stmt->handler_name, stmt->amtype); /* * Insert tuple into pg_am. @@ -187,6 +189,16 @@ get_index_am_oid(const char *amname, bool missing_ok) } /* + * get_seq_am_oid - given an access method name, look up its OID + * and verify it corresponds to an sequence AM. + */ +Oid +get_seq_am_oid(const char *amname, bool missing_ok) +{ + return get_am_type_oid(amname, AMTYPE_SEQUENCE, missing_ok); +} + +/* * get_am_oid - given an access method name, look up its OID. * The type is not checked. */ @@ -226,6 +238,8 @@ get_am_type_string(char amtype) { case AMTYPE_INDEX: return "INDEX"; + case AMTYPE_SEQUENCE: + return "SEQUENCE"; default: /* shouldn't happen */ elog(ERROR, "invalid access method type '%c'", amtype); @@ -239,9 +253,10 @@ get_am_type_string(char amtype) * This function either return valid function Oid or throw an error. */ static Oid -lookup_index_am_handler_func(List *handler_name, char amtype) +lookup_am_handler_func(List *handler_name, char amtype) { Oid handlerOid; + Oid handler_type; static const Oid funcargtypes[1] = {INTERNALOID}; if (handler_name == NIL) @@ -256,16 +271,21 @@ lookup_index_am_handler_func(List *handler_name, char amtype) switch (amtype) { case AMTYPE_INDEX: - if (get_func_rettype(handlerOid) != INDEX_AM_HANDLEROID) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("function %s must return type \"%s\"", - NameListToString(handler_name), - "index_am_handler"))); + handler_type = INDEX_AM_HANDLEROID; + break; + case AMTYPE_SEQUENCE: + handler_type = SEQ_AM_HANDLEROID; break; default: elog(ERROR, "unrecognized access method type \"%c\"", amtype); } + if (get_func_rettype(handlerOid) != handler_type) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("function %s must return type \"%s\"", + NameListToString(handler_name), + format_type_be(handler_type)))); + return handlerOid; } diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 5cb28cf..54a22da 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -680,6 +680,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 cb7a145..ab7b7a3 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 13b04e6..92ae4db 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -536,7 +536,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/sequence.c b/src/backend/commands/sequence.c index c98f981..ad4159b 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -14,16 +14,22 @@ */ #include "postgres.h" +#include "access/reloptions.h" +#include "access/seqamapi.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_am.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "commands/sequence.h" @@ -36,19 +42,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 +82,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, SeqAmRoutine *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 +200,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; + SeqAmRoutine *seqam; /* Unlogged sequences are not implemented -- not clear if useful. */ if (seq->sequence->relpersistence == RELPERSISTENCE_UNLOGGED) @@ -147,102 +234,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_seq_am_oid(seq->accessMethod, false); + else + seqamid = LOCAL_SEQAM_OID; + + seqam = GetSeqAmRoutineByAMId(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 +272,7 @@ DefineSequence(CreateSeqStmt *seq) return address; } + /* * Reset a sequence to its initial value. * @@ -267,58 +286,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]; + SeqAmRoutine *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 = GetSeqAmRoutineForRelation(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, SeqAmRoutine *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 +411,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 +431,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 +439,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 row-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 +509,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; + SeqAmRoutine *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 +534,187 @@ AlterSequence(AlterSeqStmt *stmt) return InvalidObjectAddress; } - init_sequence(relid, &elm, &seqrel); + sequence_open(seqrelid, &seqh); + seqrel = seqh.rel; + oldamid = seqrel->rd_rel->relam; + oldseqam = GetSeqAmRoutineByAMId(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_seq_am_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]; + SeqAmRoutine *newseqam; + + oldseqam = GetSeqAmRoutineByAMId(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 = GetSeqAmRoutineByAMId(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->amoptions, 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 +755,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; + SeqAmRoutine *seqam; /* open and AccessShareLock sequence */ - init_sequence(relid, &elm, &seqrel); + sequence_open(relid, &seqh); + elm = seqh.elm; + seqrel = seqh.rel; + seqam = GetSeqAmRoutineForRelation(seqrel); if (pg_class_aclcheck(elm->relid, GetUserId(), ACL_USAGE | ACL_UPDATE) != ACLCHECK_OK) @@ -577,121 +799,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 +816,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 +827,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 +888,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; + SeqAmRoutine *seqam; /* open and AccessShareLock sequence */ - init_sequence(relid, &elm, &seqrel); + sequence_open(relid, &seqh); + elm = seqh.elm; + seqrel = seqh.rel; + seqam = GetSeqAmRoutineForRelation(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 +924,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; + SeqAmRoutine *seqam; + + /* open and AccessShareLock sequence */ + sequence_open(relid, &seqh); + elm = seqh.elm; + seqrel = seqh.rel; + seqam = GetSeqAmRoutineForRelation(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 +1062,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 +1117,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 +1178,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 +1351,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 +1428,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 +1436,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 +1445,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 +1453,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 +1460,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 +1473,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 +1526,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 +1540,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 +1650,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 +1678,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 +1686,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; + SeqAmRoutine *seqam; + Oid typoutput; + bool typisvarlena; + + /* Load the sequence AM */ + sequence_open(relid, &seqh); + seqam = GetSeqAmRoutineForRelation(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; + SeqAmRoutine *seqam; + Oid typinput, + typioparam; + Datum state; + + /* Load the sequence AM */ + sequence_open(relid, &seqh); + seqam = GetSeqAmRoutineForRelation(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 +1830,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 96dc923..6e6ee5d 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/seqamapi.h" #include "access/sysattr.h" #include "access/xact.h" #include "access/xlog.h" @@ -269,6 +270,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, @@ -454,7 +456,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]; @@ -473,7 +475,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, Datum reloptions; ListCell *listptr; AttrNumber attnum; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; @@ -551,13 +552,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) + { + SeqAmRoutine *seqam; + + Assert(relamid != InvalidOid); + seqam = GetSeqAmRoutineByAMId(relamid); + reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, + NULL, true, false); - if (relkind == RELKIND_VIEW) - (void) view_reloptions(reloptions, true); + (void) am_reloptions(seqam->amoptions, 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) { @@ -678,6 +695,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, true, allowSystemTableMods, false, + relamid, typaddress); /* Store inheritance information for new rel. */ @@ -3308,7 +3326,8 @@ 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; @@ -4308,6 +4327,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; @@ -4351,8 +4373,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"); @@ -9403,12 +9425,15 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, (void) view_reloptions(newOptions, true); break; case RELKIND_INDEX: - (void) index_reloptions(rel->rd_amroutine->amoptions, newOptions, true); + (void) am_reloptions(rel->rd_amroutine->amoptions, newOptions, true); + break; + case RELKIND_SEQUENCE: + (void) am_reloptions(rel->rd_seqamroutine->amoptions, newOptions, true); break; default: ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table, view, materialized view, index, or TOAST table", + errmsg("\"%s\" is not a table, view, materialized view, index, sequence, or TOAST table", RelationGetRelationName(rel)))); break; } diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 227d382..c5b73c0 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -2115,7 +2115,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 e9d9ba2..ca0f481 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 6378db8..8c19b31 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3564,7 +3564,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; @@ -3577,7 +3579,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 854c062..5972ca6 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1627,7 +1627,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; @@ -1638,7 +1640,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 1273352..75af985 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -310,7 +310,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type event_trigger_when_list event_trigger_value_list %type event_trigger_when_item -%type enable_trigger +%type enable_trigger am_type %type copy_file_name database_name access_method_clause access_method attr_name @@ -605,7 +605,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED - MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE + MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P + MOVE NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NONE NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF @@ -3587,7 +3588,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; @@ -3609,7 +3636,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; } @@ -3617,11 +3668,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; } @@ -3680,7 +3754,7 @@ SeqOptElem: CACHE NumericOnly { $$ = makeDefElem("restart", (Node *)$3); } - ; + ; opt_by: BY {} | /* empty */ {} @@ -4715,16 +4789,21 @@ row_security_cmd: * *****************************************************************************/ -CreateAmStmt: CREATE ACCESS METHOD name TYPE_P INDEX HANDLER handler_name +CreateAmStmt: CREATE ACCESS METHOD name TYPE_P am_type HANDLER handler_name { CreateAmStmt *n = makeNode(CreateAmStmt); n->amname = $4; n->handler_name = $8; - n->amtype = AMTYPE_INDEX; + n->amtype = $6; $$ = (Node *) n; } ; +am_type: + INDEX { $$ = AMTYPE_INDEX; } + | SEQUENCE { $$ = AMTYPE_SEQUENCE; } + ; + /***************************************************************************** * * QUERIES : diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 6528494..509fa32 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/seqamapi.h" #include "catalog/dependency.h" #include "catalog/heap.h" #include "catalog/index.h" @@ -456,6 +457,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 diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4d0aac9..8b95577 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/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c index dd447cf..6c1456e 100644 --- a/src/backend/utils/adt/pseudotypes.c +++ b/src/backend/utils/adt/pseudotypes.c @@ -399,6 +399,31 @@ index_am_handler_out(PG_FUNCTION_ARGS) PG_RETURN_VOID(); /* keep compiler quiet */ } +/* + * seq_am_handler_int - input routine for pseudo-type SEQ_AM_HANDLER. + */ +Datum +seq_am_handler_in(PG_FUNCTION_ARGS) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot accept a value of type seq_am_handler"))); + + PG_RETURN_VOID(); /* keep compiler quiet */ +} + +/* + * seq_am_handler_out - output routine for pseudo-type SEQ_AM_HANDLER. + */ +Datum +seq_am_handler_out(PG_FUNCTION_ARGS) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot display a value of type seq_am_handler"))); + + PG_RETURN_VOID(); /* keep compiler quiet */ +} /* * tsm_handler_in - input routine for pseudo-type TSM_HANDLER. diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 130c06d..b9686da 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/seqamapi.h" #include "access/sysattr.h" #include "access/xact.h" #include "access/xlog.h" @@ -260,6 +261,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); @@ -424,6 +426,7 @@ static void RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; + amoptions_function amoptions = NULL; relation->rd_options = NULL; @@ -432,10 +435,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->rd_amroutine->amoptions; + break; + case RELKIND_SEQUENCE: + amoptions = relation->rd_seqamroutine->amoptions; + break; default: return; } @@ -447,8 +455,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple) */ options = extractRelOptions(tuple, GetPgClassDescriptor(), - relation->rd_rel->relkind == RELKIND_INDEX ? - relation->rd_amroutine->amoptions : NULL); + amoptions); /* * Copy parsed data into CacheMemoryContext. To guard against the @@ -1049,11 +1056,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); @@ -1555,6 +1565,34 @@ LookupOpclassInfo(Oid operatorClassOid, return opcentry; } +/* + * Initialize sequence-access-method support data for a sequence relation + */ +static void +RelationInitSequenceAccessInfo(Relation relation) +{ + HeapTuple tuple; + Form_pg_am aform; + SeqAmRoutine *result, *tmp; + + /* + * Look up the index's access method, save the OID of its handler function + */ + tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(relation->rd_rel->relam)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", + relation->rd_rel->relam); + aform = (Form_pg_am) GETSTRUCT(tuple); + Assert(aform->amtype == AMTYPE_SEQUENCE); + relation->rd_amhandler = aform->amhandler; + ReleaseSysCache(tuple); + + result = (SeqAmRoutine *) MemoryContextAlloc(CacheMemoryContext, + sizeof(SeqAmRoutine)); + tmp = GetSeqAmRoutine(relation->rd_amhandler); + memcpy(result, tmp, sizeof(SeqAmRoutine)); + relation->rd_seqamroutine = result; +} /* * formrdesc @@ -4885,6 +4923,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_seqamroutine == NULL); } /* diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 65a6cd4..074406a 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/seqamapi.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 b3ef201..513ee4a 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -4749,6 +4749,7 @@ getTables(Archive *fout, 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; @@ -4800,6 +4801,7 @@ getTables(Archive *fout, 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_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, " "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text " @@ -4842,6 +4844,7 @@ getTables(Archive *fout, 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_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, " "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text " @@ -4884,6 +4887,7 @@ getTables(Archive *fout, 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_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, " "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text " @@ -4926,6 +4930,7 @@ getTables(Archive *fout, 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, " "c.reloptions AS reloptions, " "tc.reloptions AS toast_reloptions " @@ -4966,6 +4971,7 @@ getTables(Archive *fout, 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, " "c.reloptions AS reloptions, " "tc.reloptions AS toast_reloptions " @@ -5005,6 +5011,7 @@ getTables(Archive *fout, 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, " "c.reloptions AS reloptions, " "tc.reloptions AS toast_reloptions " @@ -5044,6 +5051,7 @@ getTables(Archive *fout, 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, " "c.reloptions AS reloptions, " "NULL AS toast_reloptions " @@ -5083,6 +5091,7 @@ getTables(Archive *fout, 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 " @@ -5121,6 +5130,7 @@ getTables(Archive *fout, 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 " @@ -5155,6 +5165,7 @@ getTables(Archive *fout, 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 " @@ -5184,6 +5195,7 @@ getTables(Archive *fout, 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 " @@ -5223,6 +5235,7 @@ getTables(Archive *fout, 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 " @@ -5276,6 +5289,7 @@ getTables(Archive *fout, 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"); @@ -5341,6 +5355,10 @@ getTables(Archive *fout, 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)) @@ -11579,6 +11597,9 @@ dumpAccessMethod(Archive *fout, AccessMethodInfo *aminfo) case AMTYPE_INDEX: appendPQExpBuffer(q, "TYPE INDEX "); break; + case AMTYPE_SEQUENCE: + appendPQExpBuffer(q, "TYPE SEQUENCE "); + break; default: write_msg(NULL, "WARNING: invalid type %c of access method %s\n", aminfo->amtype, qamname); @@ -15282,7 +15303,8 @@ dumpSequence(Archive *fout, TableInfo *tbinfo) *incby, *maxv = NULL, *minv = NULL, - *cache; + *cache, + *amname = NULL; char bufm[100], bufx[100]; bool cycled; @@ -15362,6 +15384,37 @@ dumpSequence(Archive *fout, 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.", @@ -15402,6 +15455,13 @@ dumpSequence(Archive *fout, 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)); @@ -15468,6 +15528,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo) tbinfo->dobj.namespace->dobj.name, tbinfo->rolname, tbinfo->dobj.catId, 0, tbinfo->dobj.dumpId); + if (amname) + free(amname); + PQclear(res); destroyPQExpBuffer(query); @@ -15484,16 +15547,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); @@ -15506,14 +15582,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 66e6931..66af1c4 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -231,6 +231,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_class) */ 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 fd8dc91..3ca6ebe 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 469ac67..69135aa 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/seqamapi.h b/src/include/access/seqamapi.h new file mode 100644 index 0000000..a4c826a --- /dev/null +++ b/src/include/access/seqamapi.h @@ -0,0 +1,109 @@ +/*------------------------------------------------------------------------- + * + * seqamapi.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/seqamapi.h + * + *------------------------------------------------------------------------- + */ +#ifndef SEQAMAPI_H +#define SEQAMAPI_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 SeqAmRoutine +{ + NodeTag type; + + /* Custom columns needed by the AM */ + Oid StateTypeOid; + + /* Function for parsing reloptions */ + ParseRelOptions_function amoptions; + + /* Initialization */ + SeqAMInit_function Init; + + /* nextval handler */ + SeqAMAlloc_function Alloc; + + /* State manipulation functions */ + SeqAMSetval_function Setval; + SeqAMGetState_function GetState; + SeqAMSetState_function SetState; +} SeqAmRoutine; + +extern SeqAmRoutine *GetSeqAmRoutine(Oid seqamhandler); +extern SeqAmRoutine *GetSeqAmRoutineByAMId(Oid amoid); +extern SeqAmRoutine *GetSeqAmRoutineForRelation(Relation relation); +extern void ValidateSeqAmRoutine(SeqAmRoutine *routine); + +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); + +/* We need this public for serval3 */ +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 /* SEQAMAPI_H */ diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index b80d8d8..d6b3cd7 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/pg_am.h b/src/include/catalog/pg_am.h index 1116923..9c615f7 100644 --- a/src/include/catalog/pg_am.h +++ b/src/include/catalog/pg_am.h @@ -59,6 +59,7 @@ typedef FormData_pg_am *Form_pg_am; * ---------------- */ #define AMTYPE_INDEX 'i' /* index access method */ +#define AMTYPE_SEQUENCE 'S' /* sequence access method */ /* ---------------- * initial contents of pg_am @@ -84,4 +85,8 @@ DATA(insert OID = 3580 ( brin brinhandler i )); DESCR("block range index (BRIN) access method"); #define BRIN_AM_OID 3580 +DATA(insert OID = 6023 ( local seqam_local_handler S )); +DESCR("local sequence access method"); +#define LOCAL_SEQAM_OID 6023 + #endif /* PG_AM_H */ diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index a595327..c8f86f2 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -1759,6 +1759,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"); @@ -3690,6 +3694,10 @@ DATA(insert OID = 326 ( index_am_handler_in PGNSP PGUID 12 1 0 0 0 f f f f f f DESCR("I/O"); DATA(insert OID = 327 ( index_am_handler_out PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2275 "325" _null_ _null_ _null_ _null_ _null_ index_am_handler_out _null_ _null_ _null_ )); DESCR("I/O"); +DATA(insert OID = 6021 ( seq_am_handler_in PGNSP PGUID 12 1 0 0 0 f f f f f f i s 1 0 6020 "2275" _null_ _null_ _null_ _null_ _null_ seq_am_handler_in _null_ _null_ _null_ )); +DESCR("I/O"); +DATA(insert OID = 6022 ( seq_am_handler_out PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2275 "6020" _null_ _null_ _null_ _null_ _null_ seq_am_handler_out _null_ _null_ _null_ )); +DESCR("I/O"); DATA(insert OID = 3311 ( tsm_handler_in PGNSP PGUID 12 1 0 0 0 f f f f f f i s 1 0 3310 "2275" _null_ _null_ _null_ _null_ _null_ tsm_handler_in _null_ _null_ _null_ )); DESCR("I/O"); DATA(insert OID = 3312 ( tsm_handler_out PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2275 "3310" _null_ _null_ _null_ _null_ _null_ tsm_handler_out _null_ _null_ _null_ )); @@ -3701,6 +3709,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 = 6024 ( seqam_local_handler PGNSP PGUID 12 1 0 0 0 f f f f f f v s 1 0 6020 "2281" _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"); @@ -5259,6 +5271,13 @@ DESCR("pg_controldata recovery state information as a function"); DATA(insert OID = 3444 ( pg_control_init PGNSP PGUID 12 1 0 0 0 f f f f t f v s 0 0 2249 "" "{23,23,23,23,23,23,23,23,23,16,16,16,23}" "{o,o,o,o,o,o,o,o,o,o,o,o,o}" "{max_data_alignment,database_block_size,blocks_per_segment,wal_block_size,bytes_per_wal_segment,max_identifier_length,max_index_columns,max_toast_chunk_size,large_object_chunk_size,bigint_timestamps,float4_pass_by_value,float8_pass_by_value,data_page_checksum_version}" _null_ _null_ pg_control_init _null_ _null_ _null_ )); DESCR("pg_controldata init state information as a function"); +/* 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"); + + /* * Symbolic values for provolatile column: these indicate whether the result * of a function is dependent *only* on the values of its explicit arguments, diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h index 2c90b76..40214e3 100644 --- a/src/include/catalog/pg_type.h +++ b/src/include/catalog/pg_type.h @@ -696,11 +696,18 @@ DATA(insert OID = 3115 ( fdw_handler PGNSP PGUID 4 t p P f t \054 0 0 0 fdw_han #define FDW_HANDLEROID 3115 DATA(insert OID = 325 ( index_am_handler PGNSP PGUID 4 t p P f t \054 0 0 0 index_am_handler_in index_am_handler_out - - - - - i p f 0 -1 0 0 _null_ _null_ _null_ )); #define INDEX_AM_HANDLEROID 325 +DATA(insert OID = 6020 ( seq_am_handler PGNSP PGUID 4 t p P f t \054 0 0 0 seq_am_handler_in seq_am_handler_out - - - - - i p f 0 -1 0 0 _null_ _null_ _null_ )); +#define SEQ_AM_HANDLEROID 6020 DATA(insert OID = 3310 ( tsm_handler PGNSP PGUID 4 t p P f t \054 0 0 0 tsm_handler_in tsm_handler_out - - - - - i p f 0 -1 0 0 _null_ _null_ _null_ )); #define TSM_HANDLEROID 3310 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 b064eb4..c9bf431 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -139,6 +139,7 @@ extern Datum transformGenericOptions(Oid catalogId, extern ObjectAddress CreateAccessMethod(CreateAmStmt *stmt); extern void RemoveAccessMethodById(Oid amOid); extern Oid get_index_am_oid(const char *amname, bool missing_ok); +extern Oid get_seq_am_oid(const char *amname, bool missing_ok); extern Oid get_am_oid(const char *amname, bool missing_ok); extern char *get_am_name(Oid amOid); diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h index 6af60d8..e0f6439 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 7a770f4..1bceec8 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 734df77..752d3a1 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -477,6 +477,7 @@ typedef enum NodeTag T_InlineCodeBlock, /* in nodes/parsenodes.h */ T_FdwRoutine, /* in foreign/fdwapi.h */ T_IndexAmRoutine, /* in access/amapi.h */ + T_SeqAmRoutine, /* in access/seqamapi.h */ T_TsmRoutine /* in access/tsmapi.h */ } NodeTag; diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 8b958b4..859fbad 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2205,8 +2205,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; @@ -2214,8 +2216,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/builtins.h b/src/include/utils/builtins.h index 206288d..1c89b49 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -584,6 +584,8 @@ extern Datum fdw_handler_in(PG_FUNCTION_ARGS); extern Datum fdw_handler_out(PG_FUNCTION_ARGS); extern Datum index_am_handler_in(PG_FUNCTION_ARGS); extern Datum index_am_handler_out(PG_FUNCTION_ARGS); +extern Datum seq_am_handler_in(PG_FUNCTION_ARGS); +extern Datum seq_am_handler_out(PG_FUNCTION_ARGS); extern Datum tsm_handler_in(PG_FUNCTION_ARGS); extern Datum tsm_handler_out(PG_FUNCTION_ARGS); extern Datum internal_in(PG_FUNCTION_ARGS); diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index f2bebf2..4fe7e52 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -106,13 +106,18 @@ typedef struct RelationData */ bytea *rd_options; /* parsed pg_class.reloptions */ + Oid rd_amhandler; /* OID of access method's handler function */ + + /* These are non-NULL only for a sequence relation */ + struct SeqAmRoutine *rd_seqamroutine; + /* 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: */ struct HeapTupleData *rd_indextuple; /* all of pg_index tuple */ /* - * 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 @@ -126,7 +131,6 @@ typedef struct RelationData * rd_indexcxt. A relcache reset will include freeing that chunk and * setting rd_amcache = NULL. */ - Oid rd_amhandler; /* OID of index AM's handler function */ MemoryContext rd_indexcxt; /* private memory cxt for this stuff */ /* use "struct" here to avoid needing to include amapi.h: */ struct IndexAmRoutine *rd_amroutine; /* index AM's API struct */ diff --git a/src/test/regress/expected/create_am.out b/src/test/regress/expected/create_am.out index 47d6024..9242982 100644 --- a/src/test/regress/expected/create_am.out +++ b/src/test/regress/expected/create_am.out @@ -106,3 +106,22 @@ drop cascades to index grect2ind RESET enable_seqscan; RESET enable_indexscan; RESET enable_bitmapscan; +-- Checks for sequence access methods +-- Create new sequence access method which uses standard local handler +CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER seqam_local_handler; +-- Create and use sequence +CREATE SEQUENCE test_seqam USING local2; +SELECT nextval('test_seqam'::regclass); + nextval +--------- + 1 +(1 row) + +-- Try to drop and fail on dependency +DROP ACCESS METHOD local2; +ERROR: cannot drop access method local2 because other objects depend on it +DETAIL: sequence test_seqam depends on access method local2 +HINT: Use DROP ... CASCADE to drop the dependent objects too. +-- And cleanup +DROP SEQUENCE test_seqam; +DROP ACCESS METHOD local2; diff --git a/src/test/regress/expected/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/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 7c09fa3..ab0d299 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1586,7 +1586,9 @@ WHERE p1.amhandler = 0; SELECT p1.oid, p1.amname, p2.oid, p2.proname FROM pg_am AS p1, pg_proc AS p2 WHERE p2.oid = p1.amhandler AND - (p2.prorettype != 'index_am_handler'::regtype OR p2.proretset + (p2.prorettype NOT IN ('index_am_handler'::regtype, + 'seq_am_handler'::regtype) + OR p2.proretset OR p2.pronargs != 1 OR p2.proargtypes[0] != 'internal'::regtype); oid | amname | oid | proname 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 c5dfbb5..a30582e 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_am.sql b/src/test/regress/sql/create_am.sql index e2051c5..e811ea3 100644 --- a/src/test/regress/sql/create_am.sql +++ b/src/test/regress/sql/create_am.sql @@ -71,3 +71,19 @@ DROP ACCESS METHOD gist2 CASCADE; RESET enable_seqscan; RESET enable_indexscan; RESET enable_bitmapscan; + +-- Checks for sequence access methods + +-- Create new sequence access method which uses standard local handler +CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER seqam_local_handler; + +-- Create and use sequence +CREATE SEQUENCE test_seqam USING local2; +SELECT nextval('test_seqam'::regclass); + +-- Try to drop and fail on dependency +DROP ACCESS METHOD local2; + +-- And cleanup +DROP SEQUENCE test_seqam; +DROP ACCESS METHOD local2; diff --git a/src/test/regress/sql/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/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql index 6c9784a..6c44d70 100644 --- a/src/test/regress/sql/opr_sanity.sql +++ b/src/test/regress/sql/opr_sanity.sql @@ -1055,7 +1055,9 @@ WHERE p1.amhandler = 0; SELECT p1.oid, p1.amname, p2.oid, p2.proname FROM pg_am AS p1, pg_proc AS p2 WHERE p2.oid = p1.amhandler AND - (p2.prorettype != 'index_am_handler'::regtype OR p2.proretset + (p2.prorettype NOT IN ('index_am_handler'::regtype, + 'seq_am_handler'::regtype) + OR p2.proretset OR p2.pronargs != 1 OR p2.proargtypes[0] != 'internal'::regtype); 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