diff --git a/src/backend/access/Makefile b/src/backend/access/Makefile index 21721b4..818da15 100644 --- a/src/backend/access/Makefile +++ b/src/backend/access/Makefile @@ -8,6 +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 transam +SUBDIRS = brin common gin gist hash heap index nbtree rmgrdesc spgist \ + transam sequence include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index c16b38e..35818c0 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -321,6 +321,7 @@ static bool need_initialization = true; static void initialize_reloptions(void); static void parse_one_reloption(relopt_value *option, char *text_str, int text_len, bool validate); +static bytea *common_am_reloptions(RegProcedure amoptions, Datum reloptions, bool validate); /* * initialize_reloptions @@ -821,7 +822,8 @@ untransformRelOptions(Datum options) * instead. * * tupdesc is pg_class' tuple descriptor. amoptions is the amoptions regproc - * in the case of the tuple corresponding to an index, or InvalidOid otherwise. + * in the case of the tuple corresponding to an index or sequence, InvalidOid + * otherwise. */ bytea * extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, Oid amoptions) @@ -854,6 +856,9 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, Oid amoptions) case RELKIND_INDEX: options = index_reloptions(amoptions, datum, false); break; + case RELKIND_SEQUENCE: + options = sequence_reloptions(amoptions, datum, false); + break; case RELKIND_FOREIGN_TABLE: options = NULL; break; @@ -1299,13 +1304,31 @@ heap_reloptions(char relkind, Datum reloptions, bool validate) /* * Parse options for indexes. + */ +bytea * +index_reloptions(RegProcedure amoptions, Datum reloptions, bool validate) +{ + return common_am_reloptions(amoptions, reloptions, validate); +} + +/* + * Parse options for sequences. + */ +bytea * +sequence_reloptions(RegProcedure amoptions, Datum reloptions, bool validate) +{ + return common_am_reloptions(amoptions, reloptions, validate); +} + +/* + * Parse options for indexes or sequences. * * amoptions Oid of option parser * reloptions options as text[] datum * validate error flag */ -bytea * -index_reloptions(RegProcedure amoptions, Datum reloptions, bool validate) +static bytea * +common_am_reloptions(RegProcedure amoptions, Datum reloptions, bool validate) { FmgrInfo flinfo; FunctionCallInfoData fcinfo; diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequence/Makefile new file mode 100644 index 0000000..01a0dc8 --- /dev/null +++ b/src/backend/access/sequence/Makefile @@ -0,0 +1,17 @@ +#------------------------------------------------------------------------- +# +# Makefile-- +# Makefile for access/sequence +# +# IDENTIFICATION +# src/backend/access/sequence/Makefile +# +#------------------------------------------------------------------------- + +subdir = src/backend/access/sequence +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global + +OBJS = seqam.o seqlocal.o + +include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/seqam.c b/src/backend/access/sequence/seqam.c new file mode 100644 index 0000000..ce57f16 --- /dev/null +++ b/src/backend/access/sequence/seqam.c @@ -0,0 +1,428 @@ +/*------------------------------------------------------------------------- + * + * seqam.c + * sequence access method routines + * + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/sequence/seqam.c + * + * + * Sequence access method allows the SQL Standard Sequence objects to be + * managed according to either the default access method or a pluggable + * replacement. Each sequence can only use one access method at a time, + * though different sequence access methods can be in use by different + * sequences at the same time. + * + * The SQL Standard assumes that each Sequence object is completely controlled + * from the current database node, preventing any form of clustering mechanisms + * from controlling behaviour. Sequence access methods are general purpose + * though designed specifically to address the needs of Sequences working as + * part of a multi-node "cluster", though that is not defined here, nor are + * there dependencies on anything outside of this module, nor any particular + * form of clustering. + * + * The SQL Standard behaviour, also the historical PostgreSQL behaviour, is + * referred to as the "Local" SeqAm. That is also the basic default. Local + * SeqAm assumes that allocations from the sequence will be contiguous, so if + * user1 requests a range of values and is given 500-599 as values for their + * backend then the next user to make a request will be given a range starting + * with 600. + * + * The SeqAm mechanism allows us to override the Local behaviour, for use with + * clustering systems. When multiple masters can request ranges of values it + * would break the assumption of contiguous allocation. It seems likely that + * the SeqAm would also wish to control node-level caches for sequences to + * ensure good performance. The CACHE option and other options may be + * overridden by the _alloc API call, if needed, though in general having + * cacheing per backend and per node seems desirable. + * + * SeqAm allows calls to allocate a new range of values, reset the sequence to + * a new value and to define options for the AM module. The on-disk format of + * Sequences is the same for all AMs, except that each sequence has a SeqAm + * defined private-data column, am_data. + * + * We currently assume that there is no persistent state held within the SeqAm, + * so it is safe to ALTER the access method of an object without taking any + * special actions, as long as we hold an AccessExclusiveLock while we do that. + * + * SeqAMs work similarly to IndexAMs in many ways. pg_class.relam stores the + * Oid of the SeqAM, just as we do for IndexAm. The relcache stores AM + * information in much the same way for indexes and sequences, and management + * of options is similar also. + * + * Note that the SeqAM API calls are synchronous. It is up to the SeqAM to + * decide how that is handled, for example, whether there is a higher level + * cache at instance level to amortise network traffic in cluster. + * + * The SeqAM is identified by Oid of corresponding tuple in pg_seqam. There is + * no syscache for pg_seqam, though the SeqAm data is stored on the relcache + * entry for the sequence. + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/seqam.h" +#include "access/reloptions.h" +#include "access/relscan.h" +#include "access/transam.h" +#include "access/xact.h" +#include "catalog/pg_seqam.h" +#include "utils/guc.h" +#include "utils/rel.h" +#include "utils/syscache.h" + +char *default_seqam = NULL; + +#define GET_SEQAM_PROCEDURE(pname, missing_ok) \ +do { \ + procedure = &seqrel->rd_aminfo->pname; \ + if (!OidIsValid(procedure->fn_oid)) \ + { \ + RegProcedure procOid = seqrel->rd_seqam->pname; \ + if (RegProcedureIsValid(procOid)) \ + fmgr_info_cxt(procOid, procedure, seqrel->rd_indexcxt); \ + else if (!missing_ok) \ + elog(ERROR, "invalid %s regproc", CppAsString(pname)); \ + } \ +} while(0) + +/*------------------------------------------------------------------------- + * + * Sequence Access Manager API + * + * INTERFACE ROUTINES + * seqam_extra_columns - get list of extra columns needed by the am + * seqam_init - initialize sequence, also used for resetting + * seqam_alloc - allocate a new range of values for the sequence + * seqam_setval - implements the setval SQL interface + * seqam_seqparams - process the standard sequence parameters + * seqam_dump_state - dump sequence state (for pg_dump) + * seqam_restore_state - restore sequence state (for pg_dump) + * + * sequence_reloptions - process reloptions - located in reloptions.c + * + *------------------------------------------------------------------------- + */ + +/* + * seqam_extra_columns - get custom column list + */ +List * +seqam_extra_columns(Oid seqamid) +{ + FmgrInfo procedure; + HeapTuple tuple = NULL; + Form_pg_seqam seqamForm; + FunctionCallInfoData fcinfo; + Datum ret; + + tuple = SearchSysCache1(SEQAMOID, seqamid); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", seqamid); + + seqamForm = (Form_pg_seqam) GETSTRUCT(tuple); + + if (!RegProcedureIsValid(seqamForm->seqamextracols)) + return NIL; + + fmgr_info(seqamForm->seqamextracols, &procedure); + + /* + * have the seqam's proc do it's work. + */ + InitFunctionCallInfoData(fcinfo, &procedure, 0, InvalidOid, NULL, NULL); + + ret = FunctionCallInvoke(&fcinfo); + + ReleaseSysCache(tuple); + + return (List *)DatumGetPointer(ret); +} + +/* + * seqam_init - initialize/replace custom sequence am values + */ +extern void +seqam_init(Oid seqamid, List *seqparams, + List *reloptions, bool is_init, + Datum *values, bool *nulls) +{ + FmgrInfo procedure; + HeapTuple tuple = NULL; + Form_pg_seqam seqamForm; + FunctionCallInfoData fcinfo; + char *validnsps[] = {NULL, NULL}; + Datum reloptions_transformed; + bytea *reloptions_parsed; + + tuple = SearchSysCache1(SEQAMOID, seqamid); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", seqamid); + + seqamForm = (Form_pg_seqam) GETSTRUCT(tuple); + + fmgr_info(seqamForm->seqaminit, &procedure); + + /* Allow am specific options */ + validnsps[0] = NameStr(seqamForm->seqamname); + + /* + * Parse AM-specific options, convert to text array form, + * retrieve the AM-option function and then validate. + */ + reloptions_transformed = transformRelOptions((Datum) NULL, reloptions, + NULL, validnsps, false, + false); + + reloptions_parsed = sequence_reloptions(seqamForm->seqamreloptions, + reloptions_transformed, true); + + /* + * Have the seqam's proc do it's work. + */ + InitFunctionCallInfoData(fcinfo, &procedure, 5, InvalidOid, NULL, NULL); + + fcinfo.arg[0] = PointerGetDatum(seqparams); + fcinfo.arg[1] = PointerGetDatum(reloptions_parsed); + fcinfo.arg[2] = BoolGetDatum(is_init); + fcinfo.arg[3] = PointerGetDatum(values); + fcinfo.arg[4] = PointerGetDatum(nulls); + fcinfo.argnull[0] = seqparams == NULL; + fcinfo.argnull[1] = reloptions_parsed == NULL; + fcinfo.argnull[2] = false; + fcinfo.argnull[3] = false; + fcinfo.argnull[3] = false; + + FunctionCallInvoke(&fcinfo); + + ReleaseSysCache(tuple); +} + +/* + * seqam_alloc - allocate sequence values in a sequence + */ +int64 +seqam_alloc(Relation seqrel, SequenceHandle *seqh, int64 nrequested, + int64 *last) +{ + FmgrInfo *procedure; + FunctionCallInfoData fcinfo; + Datum ret; + + Assert(RelationIsValid(seqrel)); + Assert(PointerIsValid(seqrel->rd_seqam)); + Assert(OidIsValid(seqrel->rd_rel->relam)); + + GET_SEQAM_PROCEDURE(seqamalloc, false); + + /* + * have the seqam's alloc proc do it's work. + */ + InitFunctionCallInfoData(fcinfo, procedure, 4, InvalidOid, NULL, NULL); + + fcinfo.arg[0] = PointerGetDatum(seqrel); + fcinfo.arg[1] = PointerGetDatum(seqh); + fcinfo.arg[2] = Int64GetDatum(nrequested); + fcinfo.arg[3] = PointerGetDatum(last); + fcinfo.argnull[0] = false; + fcinfo.argnull[1] = false; + fcinfo.argnull[2] = false; + fcinfo.argnull[3] = false; + + ret = FunctionCallInvoke(&fcinfo); + return DatumGetInt64(ret); +} + +/* + * seqam_setval - set sequence values in a sequence + */ +void +seqam_setval(Relation seqrel, SequenceHandle *seqh, int64 new_value) +{ + FmgrInfo *procedure; + FunctionCallInfoData fcinfo; + + Assert(RelationIsValid(seqrel)); + Assert(PointerIsValid(seqrel->rd_seqam)); + Assert(OidIsValid(seqrel->rd_rel->relam)); + + GET_SEQAM_PROCEDURE(seqamsetval, true); + + if (!OidIsValid(procedure->fn_oid)) + return; + + /* + * have the seqam's setval proc do it's work. + */ + InitFunctionCallInfoData(fcinfo, procedure, 3, InvalidOid, NULL, NULL); + + fcinfo.arg[0] = PointerGetDatum(seqrel); + fcinfo.arg[1] = PointerGetDatum(seqh); + fcinfo.arg[2] = Int64GetDatum(new_value); + fcinfo.argnull[0] = false; + fcinfo.argnull[1] = false; + fcinfo.argnull[2] = false; + + FunctionCallInvoke(&fcinfo); +} + +/* + * seqam_dump_state - pg_dump support + */ +char * +seqam_dump_state(Relation seqrel, SequenceHandle *seqh) +{ + FmgrInfo procedure; + Datum ret; + FunctionCallInfoData fcinfo; + + Assert(RelationIsValid(seqrel)); + Assert(PointerIsValid(seqrel->rd_seqam)); + Assert(OidIsValid(seqrel->rd_rel->relam)); + + fmgr_info(seqrel->rd_seqam->seqamdump, &procedure); + + /* + * have the seqam's setval proc do it's work. + */ + InitFunctionCallInfoData(fcinfo, &procedure, 2, InvalidOid, NULL, NULL); + + fcinfo.arg[0] = PointerGetDatum(seqrel); + fcinfo.arg[1] = PointerGetDatum(seqh); + fcinfo.argnull[0] = false; + fcinfo.argnull[1] = false; + + ret = FunctionCallInvoke(&fcinfo); + + return DatumGetCString(ret); +} + +/* + * seqam_restore_state - restore from pg_dump + */ +void +seqam_restore_state(Relation seqrel, SequenceHandle *seqh, char *state) +{ + FmgrInfo procedure; + FunctionCallInfoData fcinfo; + + Assert(RelationIsValid(seqrel)); + Assert(PointerIsValid(seqrel->rd_seqam)); + Assert(OidIsValid(seqrel->rd_rel->relam)); + + fmgr_info(seqrel->rd_seqam->seqamrestore, &procedure); + + /* + * have the seqam's setval proc do it's work. + */ + InitFunctionCallInfoData(fcinfo, &procedure, 4, InvalidOid, NULL, NULL); + + fcinfo.arg[0] = PointerGetDatum(seqrel); + fcinfo.arg[1] = PointerGetDatum(seqh); + fcinfo.arg[2] = CStringGetDatum(state); + fcinfo.argnull[0] = false; + fcinfo.argnull[1] = false; + fcinfo.argnull[2] = false; + + FunctionCallInvoke(&fcinfo); +} + + +/*------------------------------------------------------------ + * + * Sequence Access Manager management functions + * + *------------------------------------------------------------ + */ + +/* check_hook: validate new default_sequenceam */ +bool +check_default_seqam(char **newval, void **extra, GucSource source) +{ + if (**newval == '\0') + return true; + + /* + * If we aren't inside a transaction, we cannot do database access so + * cannot verify the name. Must accept the value on faith. + */ + if (IsTransactionState()) + { + if (!OidIsValid(get_seqam_oid(*newval, true))) + { + /* + * When source == PGC_S_TEST, we are checking the argument of an + * ALTER DATABASE SET or ALTER USER SET command. Value may + * be created later. Because of that, issue a NOTICE if source == + * PGC_S_TEST, but accept the value anyway. + */ + if (source == PGC_S_TEST) + { + ereport(NOTICE, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("sequence manager \"%s\" does not exist", + *newval))); + } + else + { + GUC_check_errdetail("sequence manager \"%s\" does not exist.", + *newval); + return false; + } + } + } + return true; +} + +/* + * GetDefaultSeqAM -- get the OID of the current default sequence AM + * + * This exists to hide (and possibly optimize the use of) the + * default_seqam GUC variable. + */ +Oid +GetDefaultSeqAM(void) +{ + /* Fast path for default_tablespace == "" */ + if (default_seqam == NULL || default_seqam[0] == '\0') + return LOCAL_SEQAM_OID; + + return get_seqam_oid(default_seqam, false); +} + +/* + * get_seqam_oid - given a sequence AM name, look up the OID + * + * If missing_ok is false, throw an error if SeqAM name not found. If true, + * just return InvalidOid. + */ +Oid +get_seqam_oid(const char *amname, bool missing_ok) +{ + Oid result; + HeapTuple tuple; + + /* look up the access method */ + tuple = SearchSysCache1(SEQAMNAME, PointerGetDatum(amname)); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + { + result = HeapTupleGetOid(tuple); + ReleaseSysCache(tuple); + } + else + result = InvalidOid; + + if (!OidIsValid(result) && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("squence access method \"%s\" does not exist", + amname))); + return result; +} diff --git a/src/backend/access/sequence/seqlocal.c b/src/backend/access/sequence/seqlocal.c new file mode 100644 index 0000000..9cc5f11 --- /dev/null +++ b/src/backend/access/sequence/seqlocal.c @@ -0,0 +1,405 @@ +/*------------------------------------------------------------------------- + * + * seqlocal.c + * Local sequence access manager + * + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/sequence/seqlocal.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/reloptions.h" +#include "access/seqam.h" +#include "catalog/pg_type.h" +#include "commands/defrem.h" +#include "miscadmin.h" +#include "utils/builtins.h" +#include "utils/int8.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 + +/* Definition of additional columns for local sequence. */ +typedef struct FormLocalSequence +{ + FormData_pg_sequence seq; + int64 last_value; + int64 log_cnt; + bool is_called; +} FormLocalSequence; + +SeqAMColumnData seqam_local_cols[] = { + {"last_value", INT8OID, -1, true}, + {"log_cnt", INT8OID, -1, true}, + {"is_called", BOOLOID, -1, true} +}; +#define SEQAM_LOCAL_EXTRACOLS_CNT 3 +#define SEQAM_LOCAL_COL_LASTVAL SEQ_COL_LASTCOL + 1 +#define SEQAM_LOCAL_COL_LOGCNT SEQ_COL_LASTCOL + 2 +#define SEQAM_LOCAL_COL_CALLED SEQ_COL_LASTCOL + 3 + +/* + * seqam_local_extracols() + * + * Get definitions for extra columns needed by a local sequence + */ +Datum +seqam_local_extracols(PG_FUNCTION_ARGS) +{ + List *ret = NIL; + int i; + + for (i = 0; i < SEQAM_LOCAL_EXTRACOLS_CNT; i++) + ret = lappend(ret, &seqam_local_cols[i]); + + PG_RETURN_POINTER(ret); +} + +/* + * seqam_local_reloptions() + * + * Parse and verify the reloptions of a local sequence. + */ +Datum +seqam_local_reloptions(PG_FUNCTION_ARGS) +{ + Datum reloptions = PG_GETARG_DATUM(0); + bool validate = PG_GETARG_BOOL(1); + bytea *result; + + result = default_reloptions(reloptions, validate, RELOPT_KIND_SEQUENCE); + if (result) + PG_RETURN_BYTEA_P(result); + + PG_RETURN_NULL(); +} + +/* + * seqam_local_init() + * + * Initialize local sequence + */ +Datum +seqam_local_init(PG_FUNCTION_ARGS) +{ + List *params = (List *)(PG_ARGISNULL(0) ? NULL : PG_GETARG_POINTER(0)); + bool is_init = PG_GETARG_BOOL(2); + Datum *values = (Datum *)PG_GETARG_POINTER(3); + bool *nulls = (bool *)PG_GETARG_POINTER(4); + + DefElem *restart_value = NULL; + int64 last_value, + min_value, + max_value; + ListCell *param; + + foreach(param, params) + { + DefElem *defel = (DefElem *) lfirst(param); + if (strcmp(defel->defname, "restart") == 0) + { + /* The redundacy check is done in the main sequence code. */ + restart_value = defel; + break; + } + } + + /* RESTART [WITH] */ + if (restart_value != NULL) + { + Datum last_value; + + if (restart_value->arg != NULL) + last_value = defGetInt64(restart_value); + else + last_value = values[SEQ_COL_STARTVAL - 1]; + + values[SEQAM_LOCAL_COL_LASTVAL - 1] = last_value; + values[SEQAM_LOCAL_COL_CALLED - 1] = BoolGetDatum(false); + } + else if (is_init) + { + values[SEQAM_LOCAL_COL_LASTVAL - 1] = values[SEQ_COL_STARTVAL - 1]; + values[SEQAM_LOCAL_COL_CALLED - 1] = BoolGetDatum(false); + } + + last_value = DatumGetInt64(values[SEQAM_LOCAL_COL_LASTVAL - 1]); + min_value = DatumGetInt64(values[SEQ_COL_MINVALUE - 1]); + max_value = DatumGetInt64(values[SEQ_COL_MAXVALUE - 1]); + + /* crosscheck RESTART (or current value, if changing MIN/MAX) */ + if (last_value < min_value) + { + char bufs[100], + bufm[100]; + + snprintf(bufs, sizeof(bufs), INT64_FORMAT, last_value); + snprintf(bufm, sizeof(bufm), INT64_FORMAT, min_value); + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("RESTART value (%s) cannot be less than MINVALUE (%s)", + bufs, bufm))); + } + if (last_value > max_value) + { + char bufs[100], + bufm[100]; + + snprintf(bufs, sizeof(bufs), INT64_FORMAT, last_value); + snprintf(bufm, sizeof(bufm), INT64_FORMAT, max_value); + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("RESTART value (%s) cannot be greater than MAXVALUE (%s)", + bufs, bufm))); + } + + /* log_cnt */ + values[SEQAM_LOCAL_COL_LOGCNT - 1] = Int64GetDatum(0); + + nulls[SEQAM_LOCAL_COL_LASTVAL - 1] = false; + nulls[SEQAM_LOCAL_COL_LOGCNT - 1] = false; + nulls[SEQAM_LOCAL_COL_CALLED - 1] = false; + + PG_RETURN_VOID(); +} + +/* + * seqam_local_alloc() + * + * Allocate new range of values for a local sequence. + */ +Datum +seqam_local_alloc(PG_FUNCTION_ARGS) +{ + Relation seqrel = (Relation) PG_GETARG_POINTER(0); + SequenceHandle *seqh = (SequenceHandle*) PG_GETARG_POINTER(1); + int64 nrequested = PG_GETARG_INT64(2); + int64 *last = (int64 *) PG_GETARG_POINTER(3); + FormLocalSequence *seq; + int64 incby, + maxv, + minv, + log, + fetch, + result, + next, + rescnt = 0; + bool is_cycled, + logit = false; + + seq = (FormLocalSequence *) GETSTRUCT(sequence_read_tuple(seqh)); + + next = result = seq->last_value; + incby = seq->seq.increment_by; + maxv = seq->seq.max_value; + minv = seq->seq.min_value; + is_cycled = seq->seq.is_cycled; + fetch = nrequested; + log = seq->log_cnt; + + /* We are returning last_value if not is_called so fetch one less value. */ + if (!seq->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 || !seq->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 (seq->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); + + START_CRIT_SECTION(); + + /* Log our cached data. */ + if (logit) + { + seq->last_value = next; + seq->is_called = true; + seq->log_cnt = 0; + + sequence_save_tuple(seqh, NULL, true); + } + + /* 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 */ + + sequence_save_tuple(seqh, NULL, false); + + END_CRIT_SECTION(); + + PG_RETURN_INT64(result); +} + +/* + * seqam_local_setval() + * + * Set value of a local sequence + */ +Datum +seqam_local_setval(PG_FUNCTION_ARGS) +{ + SequenceHandle *seqh = (SequenceHandle*) PG_GETARG_POINTER(1); + int64 next = PG_GETARG_INT64(2); + FormLocalSequence *seq; + + seq = (FormLocalSequence *) GETSTRUCT(sequence_read_tuple(seqh)); + + seq->last_value = next; /* last fetched number */ + seq->is_called = true; + seq->log_cnt = 0; /* how much is logged */ + + sequence_save_tuple(seqh, NULL, true); + + PG_RETURN_VOID(); +} + +/* + * seqam_local_dump() + * + * Dump state of a local sequence (for pg_dump) + * + * Format is: ', ' + */ +Datum +seqam_local_dump(PG_FUNCTION_ARGS) +{ + SequenceHandle *seqh = (SequenceHandle*) PG_GETARG_POINTER(1); + FormLocalSequence *seq; + char buf[19 + 1 + 1 + 1 + 1]; /* int64 + colon + space + t/f + null byte */ + char *result; + + + seq = (FormLocalSequence *) GETSTRUCT(sequence_read_tuple(seqh)); + + pg_lltoa(seq->last_value, buf); + strcat(buf, seq->is_called ? ", t" : ", f"); + result = pstrdup(buf); + + PG_RETURN_CSTRING(result); +} + +/* + * seqam_local_restore() + * + * Restore previously dumpred state of local sequence (used by pg_dump) + * + * Format is: ', ' +*/ +Datum +seqam_local_restore(PG_FUNCTION_ARGS) +{ + SequenceHandle *seqh = (SequenceHandle*) PG_GETARG_POINTER(1); + char *state = PG_GETARG_CSTRING(2); + FormLocalSequence *seq; + int64 last_value; + bool is_called; + char *buf; + char *ptr = state; + int len; + + /* Check that we can find comma. */ + while (*ptr != ',' && *ptr != '\0') + ptr++; + + if (*ptr == '\0') + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for for local sequence state: \"%s\"", + state))); + + /* + * Copy the part of the string before comma into work buffer + * for scanint8, it will handle leading/trailing whitespace correctly. + */ + len = ptr - state; + buf = palloc(len + 1); + memcpy(buf, state, len); + buf[len] = '\0'; + scanint8(buf, false, &last_value); + + /* Skip leading/trailing whitespace for parse_bool_with_len. */ + ptr++; + while (isspace((unsigned char) *ptr)) + ptr++; + + len = strlen(ptr); + while (len > 0 && isspace((unsigned char) ptr[len - 1])) + len--; + + /* Parse out the boolean */ + if (!parse_bool_with_len(ptr, len, &is_called)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for for local sequence state: \"%s\"", + state))); + + pfree(buf); + + seq = (FormLocalSequence *) GETSTRUCT(sequence_read_tuple(seqh)); + + seq->last_value = last_value; + seq->is_called = is_called; + + sequence_save_tuple(seqh, NULL, true); + + PG_RETURN_VOID(); +} diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index a403c64..147c571 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -35,7 +35,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\ pg_statistic.h pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \ pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \ pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \ - pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \ + pg_authid.h pg_auth_members.h pg_seqam.h pg_shdepend.h pg_shdescription.h \ pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \ pg_ts_parser.h pg_ts_template.h pg_extension.h \ pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \ diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index ba5b938..6689915 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -14,15 +14,21 @@ */ #include "postgres.h" +#include "access/reloptions.h" +#include "access/seqam.h" +#include "access/transam.h" #include "access/htup_details.h" #include "access/multixact.h" #include "access/transam.h" +#include "access/xact.h" #include "access/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_seqam.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "commands/sequence.h" @@ -35,19 +41,13 @@ #include "storage/smgr.h" #include "utils/acl.h" #include "utils/builtins.h" +#include "utils/int8.h" #include "utils/lsyscache.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 @@ -80,6 +80,14 @@ typedef SeqTableData *SeqTable; static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */ +struct SequenceHandle +{ + SeqTable elm; + Relation rel; + Buffer buf; + HeapTuple tup; +}; + /* * last_used_seq is updated by nextval() to point to the last used * sequence. @@ -90,13 +98,124 @@ 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 void seqrel_update_relam(Oid seqoid, Oid seqamid); +static Oid get_new_seqam_oid(Oid oldAM, char *accessMethod); + + +/* + * 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_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; + + return coldef; +} + +/* + * Create the relation for sequence. + */ +static Oid +DefineSequenceRelation(RangeVar *rel, List *cols, + bool if_not_exists, Oid ownerId) +{ + CreateStmt *stmt = makeNode(CreateStmt); + + stmt->relation = rel; + stmt->tableElts = cols; + stmt->inhRelations = NIL; + stmt->constraints = NIL; + stmt->options = NIL; + stmt->oncommit = ONCOMMIT_NOOP; + stmt->tablespacename = NULL; + stmt->if_not_exists = if_not_exists; + + return DefineRelation(stmt, RELKIND_SEQUENCE, ownerId); +} + +/* + * Add additional sequence am columns to the sequence column definition list. + */ +static List * +BuildSeqColumnDefList(List *amcols) +{ + List *seqcols; + ListCell *amcol; + int colid; + + seqcols = NIL; + for (colid = SEQ_COL_FIRSTCOL; colid <= SEQ_COL_LASTCOL; colid++) + { + ColumnDef *coldef = makeSeqColumnDef(); + + coldef->is_not_null = true; + + switch (colid) + { + case SEQ_COL_NAME: + coldef->typeName = makeTypeNameFromOid(NAMEOID, -1); + coldef->colname = "sequence_name"; + break; + 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; + } + seqcols = lappend(seqcols, coldef); + } + + foreach(amcol, amcols) + { + SeqAMColumn amcolumn = lfirst(amcol); + + ColumnDef *coldef = makeSeqColumnDef(); + + coldef->typeName = makeTypeNameFromOid(amcolumn->coltyp, + amcolumn->coltypmod); + coldef->colname = amcolumn->colname; + coldef->is_not_null = amcolumn->colnotnull; + seqcols = lappend(seqcols, coldef); + } + + return seqcols; +} /* @@ -108,15 +227,17 @@ DefineSequence(CreateSeqStmt *seq) { FormData_pg_sequence new; List *owned_by; - CreateStmt *stmt = makeNode(CreateStmt); Oid seqoid; + Oid seqamid; Relation rel; HeapTuple tuple; TupleDesc tupDesc; - Datum value[SEQ_COL_LASTCOL]; - bool null[SEQ_COL_LASTCOL]; - int i; + Datum *value; + bool *null; + int colid; NameData name; + List *seqcols, + *amcols; /* Unlogged sequences are not implemented -- not clear if useful. */ if (seq->sequence->relpersistence == RELPERSISTENCE_UNLOGGED) @@ -142,104 +263,75 @@ DefineSequence(CreateSeqStmt *seq) } } - /* Check and set all option values */ + /* Check and set all param values */ init_params(seq->options, true, &new, &owned_by); + /* Build column definitions. */ + seqamid = get_new_seqam_oid(InvalidOid, seq->accessMethod); + amcols = seqam_extra_columns(seqamid); + + seqcols = BuildSeqColumnDefList(amcols); + + value = palloc0(list_length(seqcols) * sizeof(Datum)); + null = palloc0(list_length(seqcols) * sizeof(bool)); + /* - * Create relation (and fill value[] and null[] for the tuple) + * Fill value[] and null[] for the tuple. */ - stmt->tableElts = NIL; - for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) + for (colid = SEQ_COL_FIRSTCOL; colid <= SEQ_COL_LASTCOL; colid++) { - ColumnDef *coldef = makeNode(ColumnDef); + null[colid - 1] = false; - 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) + switch (colid) { 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); + value[colid - 1] = NameGetDatum(&name); break; case SEQ_COL_STARTVAL: - coldef->typeName = makeTypeNameFromOid(INT8OID, -1); - coldef->colname = "start_value"; - value[i - 1] = Int64GetDatumFast(new.start_value); + value[colid - 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); + value[colid - 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); + value[colid - 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); + value[colid - 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); + value[colid - 1] = Int64GetDatumFast(new.cache_value); 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); + value[colid - 1] = BoolGetDatum(new.is_cycled); break; } - stmt->tableElts = lappend(stmt->tableElts, coldef); } - stmt->relation = seq->sequence; - stmt->inhRelations = NIL; - stmt->constraints = NIL; - stmt->options = NIL; - stmt->oncommit = ONCOMMIT_NOOP; - stmt->tablespacename = NULL; - stmt->if_not_exists = seq->if_not_exists; + /* Let AM fill the value[] and null[] for the tuple as well. */ + seqam_init(seqamid, seq->options, seq->amoptions, true, + value, null); - seqoid = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId); + /* Create the sequence relation */ + seqoid = DefineSequenceRelation(seq->sequence, seqcols, + seq->if_not_exists, seq->ownerId); Assert(seqoid != InvalidOid); + /* + * After we've created the sequence's relation in pg_class, update + * the relam to a non-default value, if requested. We perform this + * as a separate update to avoid invasive changes in normal code + * paths and to keep the code similar between CREATE and ALTER. + */ + seqrel_update_relam(seqoid, seqamid); + rel = heap_open(seqoid, AccessExclusiveLock); - tupDesc = RelationGetDescr(rel); /* now initialize the sequence's data */ + tupDesc = RelationGetDescr(rel); tuple = heap_form_tuple(tupDesc, value, null); + fill_seq_with_data(rel, tuple); /* process OWNED BY if given */ @@ -266,56 +358,68 @@ DefineSequence(CreateSeqStmt *seq) void ResetSequence(Oid seq_relid) { - Relation seq_rel; - SeqTable elm; - Form_pg_sequence seq; - Buffer buf; - HeapTupleData seqtuple; HeapTuple tuple; + HeapTuple newtup; + Relation seqrel; + TupleDesc tupDesc; + Datum *values; + bool *nulls; + bool *replaces; + SequenceHandle *seqh; /* - * 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); + seqh = sequence_open(seq_relid); + tuple = sequence_read_tuple(seqh); /* * 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) + * Tell AM to reset the sequence. + * This fakes the ALTER SEQUENCE RESTART command from the + * Sequence AM perspective. */ - seq = (Form_pg_sequence) GETSTRUCT(tuple); - seq->last_value = seq->start_value; - seq->is_called = false; - seq->log_cnt = 0; + seqrel = seqh->rel; + tupDesc = RelationGetDescr(seqrel); + values = (Datum *) palloc(tupDesc->natts * sizeof(Datum)); + nulls = (bool *) palloc(tupDesc->natts * sizeof(bool)); + replaces = (bool *) palloc(tupDesc->natts * sizeof(bool)); + memset(replaces, true, tupDesc->natts * sizeof(bool)); + + heap_deform_tuple(tuple, tupDesc, values, nulls); + + seqam_init(seqrel->rd_rel->relam, + list_make1(makeDefElem("restart", NULL)), NULL, false, + values, nulls); + + newtup = heap_modify_tuple(tuple, tupDesc, values, nulls, replaces); /* * 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(seqh->rel, 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(seqh->rel, newtup); /* 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); } /* @@ -368,23 +472,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(); @@ -400,16 +488,23 @@ Oid AlterSequence(AlterSeqStmt *stmt) { Oid relid; + Oid oldamid; + Oid seqamid; SeqTable elm; + HeapTuple tuple; + HeapTuple newtup; Relation seqrel; - Buffer buf; - HeapTupleData seqtuple; - Form_pg_sequence seq; - FormData_pg_sequence new; + Form_pg_sequence new; List *owned_by; + TupleDesc tupDesc; + Datum *values; + bool *nulls; + bool *replaces; + SequenceHandle *seqh; /* Open and lock sequence. */ - relid = RangeVarGetRelid(stmt->sequence, AccessShareLock, stmt->missing_ok); + relid = RangeVarGetRelid(stmt->sequence, AccessExclusiveLock, stmt->missing_ok); + if (relid == InvalidOid) { ereport(NOTICE, @@ -418,7 +513,9 @@ AlterSequence(AlterSeqStmt *stmt) return InvalidOid; } - init_sequence(relid, &elm, &seqrel); + seqh = sequence_open(relid); + elm = seqh->elm; + seqrel = seqh->rel; /* allow ALTER to sequence owner only */ if (!pg_class_ownercheck(relid, GetUserId())) @@ -426,48 +523,98 @@ AlterSequence(AlterSeqStmt *stmt) 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); /* 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; + tupDesc = RelationGetDescr(seqrel); + values = (Datum *) palloc(tupDesc->natts * sizeof(Datum)); + nulls = (bool *) palloc(tupDesc->natts * sizeof(bool)); - /* Now okay to update the on-disk tuple */ - START_CRIT_SECTION(); + heap_deform_tuple(tuple, tupDesc, values, nulls); - memcpy(seq, &new, sizeof(FormData_pg_sequence)); + oldamid = seqrel->rd_rel->relam; + seqamid = get_new_seqam_oid(seqrel->rd_rel->relam, stmt->accessMethod); - MarkBufferDirty(buf); - - /* XLOG stuff */ - if (RelationNeedsWAL(seqrel)) + /* + * If we are changing sequence AM, we need to replace + * the sequence relation. + */ + if (seqamid != oldamid) { - xl_seq_rec xlrec; - XLogRecPtr recptr; - Page page = BufferGetPage(buf); + List *oldamcols = seqam_extra_columns(oldamid); + List *newamcols = seqam_extra_columns(seqamid); + ListCell *amcol; + List *atcmds = NIL; - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + sequence_release_tuple(seqh); - xlrec.node = seqrel->rd_node; - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); + RelationSetNewRelfilenode(seqrel, seqrel->rd_rel->relpersistence, + InvalidTransactionId, InvalidMultiXactId); - XLogRegisterData((char *) seqtuple.t_data, seqtuple.t_len); + foreach(amcol, oldamcols) + { + SeqAMColumn amcolumn = lfirst(amcol); + AlterTableCmd *cmd = makeNode(AlterTableCmd); + + cmd->subtype = AT_DropColumnFromSequence; + cmd->name = amcolumn->colname; + cmd->behavior = DROP_RESTRICT; + cmd->missing_ok = TRUE; + atcmds = lappend(atcmds, cmd); + } - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); + foreach(amcol, newamcols) + { + SeqAMColumn amcolumn = lfirst(amcol); + ColumnDef *coldef = makeSeqColumnDef(); + AlterTableCmd *cmd = makeNode(AlterTableCmd); + + coldef->typeName = makeTypeNameFromOid(amcolumn->coltyp, + amcolumn->coltypmod); + coldef->colname = amcolumn->colname; + coldef->is_not_null = amcolumn->colnotnull; + + cmd->subtype = AT_AddColumnToSequence; + cmd->def = (Node *) coldef; + atcmds = lappend(atcmds, cmd); + } - PageSetLSN(page, recptr); - } + AlterTableInternal(relid, atcmds, false); - END_CRIT_SECTION(); + /* Let sequence AM update the tuple. */ + tupDesc = RelationGetDescr(seqrel); - UnlockReleaseBuffer(buf); + values = (Datum *) repalloc(values, tupDesc->natts * sizeof(Datum)); + nulls = (bool *) repalloc(nulls, tupDesc->natts * sizeof(bool)); + + seqam_init(seqamid, stmt->options, stmt->amoptions, seqamid != oldamid, + values, nulls); + + newtup = heap_form_tuple(tupDesc, values, nulls); + fill_seq_with_data(seqh->rel, newtup); + + seqrel_update_relam(relid, seqamid); + } + else + { + /* Let sequence AM update the tuple. */ + replaces = (bool *) palloc(tupDesc->natts * sizeof(bool)); + memset(replaces, true, tupDesc->natts * sizeof(bool)); + seqam_init(seqamid, stmt->options, stmt->amoptions, seqamid != oldamid, + values, nulls); + newtup = heap_modify_tuple(tuple, tupDesc, values, nulls, replaces); + sequence_save_tuple(seqh, newtup, true); + } + + /* 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; /* process OWNED BY if given */ if (owned_by) @@ -475,7 +622,7 @@ AlterSequence(AlterSeqStmt *stmt) InvokeObjectPostAlterHook(RelationRelationId, relid, 0); - relation_close(seqrel, NoLock); + sequence_close(seqh); return relid; } @@ -516,29 +663,24 @@ 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; /* open and AccessShareLock sequence */ - init_sequence(relid, &elm, &seqrel); + seqh = sequence_open(relid); + elm = seqh->elm; + seqrel = seqh->rel; if (pg_class_aclcheck(elm->relid, GetUserId(), ACL_USAGE | ACL_UPDATE) != ACLCHECK_OK) @@ -556,121 +698,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); + seq_form = (Form_pg_sequence) GETSTRUCT(sequence_read_tuple(seqh)); - 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]; - - 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 */ @@ -679,261 +715,119 @@ nextval_internal(Oid relid) last_used_seq = elm; - /* ready to change the on-disk (or really, in-buffer) tuple */ - START_CRIT_SECTION(); - - /* - * We must mark the buffer dirty before doing XLogInsert(); see notes in - * SyncOneBuffer(). However, we don't apply the desired changes just yet. - * This looks like a violation of the buffer update protocol, but it is in - * fact safe because we hold exclusive lock on the buffer. Any other - * process, including a checkpoint, that tries to examine the buffer - * contents will block until we release the lock, and then will see the - * final state that we install below. - */ - MarkBufferDirty(buf); - - /* XLOG stuff */ - if (logit && RelationNeedsWAL(seqrel)) - { - 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_close(seqh); return result; } + Datum 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); + seqh = sequence_open(relid); - 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)))); - - if (!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)))); - - result = elm->last; - - relation_close(seqrel, NoLock); - - PG_RETURN_INT64(result); -} - -Datum -lastval(PG_FUNCTION_ARGS) -{ - Relation seqrel; - int64 result; - - if (last_used_seq == NULL) - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("lastval is not yet defined in this session"))); - - /* Someone may have dropped the sequence since the last nextval() */ - if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(last_used_seq->relid))) - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("lastval is not yet defined in this session"))); - - seqrel = open_share_lock(last_used_seq); - - /* nextval() must have already been called for this sequence */ - Assert(last_used_seq->last_valid); - - if (pg_class_aclcheck(last_used_seq->relid, GetUserId(), - ACL_SELECT | ACL_USAGE) != ACLCHECK_OK) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("permission denied for sequence %s", - RelationGetRelationName(seqrel)))); - - result = last_used_seq->last; - relation_close(seqrel, NoLock); - - PG_RETURN_INT64(result); -} - -/* - * 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. - */ -static void -do_setval(Oid relid, int64 next, bool iscalled) -{ - SeqTable elm; - Relation seqrel; - Buffer buf; - HeapTupleData seqtuple; - Form_pg_sequence seq; - - /* open and AccessShareLock sequence */ - init_sequence(relid, &elm, &seqrel); - - if (pg_class_aclcheck(elm->relid, GetUserId(), ACL_UPDATE) != ACLCHECK_OK) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("permission denied for sequence %s", - RelationGetRelationName(seqrel)))); + RelationGetRelationName(seqh->rel)))); - /* read-only transactions may only modify temp sequences */ - if (!seqrel->rd_islocaltemp) - PreventCommandIfReadOnly("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); + if (!seqh->elm->last_valid) 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; - } - - /* In any case, forget any future cached numbers */ - elm->cached = elm->last; - - /* ready to change the on-disk (or really, in-buffer) tuple */ - START_CRIT_SECTION(); + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("currval of sequence \"%s\" is not yet defined in this session", + RelationGetRelationName(seqh->rel)))); - seq->last_value = next; /* last fetched number */ - seq->is_called = iscalled; - seq->log_cnt = 0; + result = seqh->elm->last; - MarkBufferDirty(buf); + sequence_close(seqh); - /* XLOG stuff */ - if (RelationNeedsWAL(seqrel)) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - Page page = BufferGetPage(buf); + PG_RETURN_INT64(result); +} - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); +Datum +lastval(PG_FUNCTION_ARGS) +{ + Relation seqrel; + int64 result; - xlrec.node = seqrel->rd_node; - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) seqtuple.t_data, seqtuple.t_len); + if (last_used_seq == NULL) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("lastval is not yet defined in this session"))); - recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); + /* Someone may have dropped the sequence since the last nextval() */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(last_used_seq->relid))) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("lastval is not yet defined in this session"))); - PageSetLSN(page, recptr); - } + seqrel = open_share_lock(last_used_seq); - END_CRIT_SECTION(); + /* nextval() must have already been called for this sequence */ + Assert(last_used_seq->last_valid); - UnlockReleaseBuffer(buf); + if (pg_class_aclcheck(last_used_seq->relid, GetUserId(), + ACL_SELECT | ACL_USAGE) != ACLCHECK_OK) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied for sequence %s", + RelationGetRelationName(seqrel)))); + result = last_used_seq->last; relation_close(seqrel, NoLock); + + PG_RETURN_INT64(result); } /* - * Implement the 2 arg setval procedure. - * See do_setval for discussion. + * Implement the setval procedure. */ Datum setval_oid(PG_FUNCTION_ARGS) { Oid relid = PG_GETARG_OID(0); int64 next = PG_GETARG_INT64(1); + SeqTable elm; + Relation seqrel; + SequenceHandle *seqh; - do_setval(relid, next, true); + /* open and AccessShareLock sequence */ + seqh = sequence_open(relid); + elm = seqh->elm; + seqrel = seqh->rel; - PG_RETURN_INT64(next); -} + 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)))); -/* - * 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); + /* read-only transactions may only modify temp sequences */ + if (!seqrel->rd_islocaltemp) + PreventCommandIfReadOnly("setval()"); - do_setval(relid, next, iscalled); + seqam_setval(seqrel, seqh, next); + + /* Reset local cached data */ + elm->last = next; /* last returned number */ + elm->last_valid = true; + elm->cached = elm->last; + + last_used_seq = elm; + + sequence_close(seqh); PG_RETURN_INT64(next); } - /* * Open the sequence and acquire AccessShareLock if needed * @@ -993,21 +887,21 @@ 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) +SequenceHandle * +sequence_open(Oid seq_relid) { - SeqTable elm; - Relation seqrel; - bool found; + SequenceHandle *seqh; + SeqTable elm; + Relation seqrel; + bool found; /* Find or create a hash table entry for this sequence */ if (seqhashtab == NULL) create_seq_hashtable(); - elm = (SeqTable) hash_search(seqhashtab, &relid, HASH_ENTER, &found); + elm = (SeqTable) hash_search(seqhashtab, &seq_relid, HASH_ENTER, &found); /* * Initialize the new hash table entry if it did not exist already. @@ -1048,45 +942,60 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel) elm->cached = elm->last; } + seqh = (SequenceHandle *) palloc(sizeof(SequenceHandle)); /* Return results */ - *p_elm = elm; - *p_rel = seqrel; + seqh->elm = elm; + seqh->rel = seqrel; + seqh->buf = InvalidBuffer; + seqh->tup = NULL; + + return seqh; } +/* + * Given the sequence handle, unlock the page buffer and close the relation + */ +void +sequence_close(SequenceHandle *seqh) +{ + sequence_release_tuple(seqh); + + 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) +HeapTuple +sequence_read_tuple(SequenceHandle *seqh) { Page page; + Buffer buf; ItemId lp; sequence_magic *sm; - Form_pg_sequence seq; + Form_pg_sequence seq_form; + + if (HeapTupleIsValid(seqh->tup)) + return seqh->tup; - *buf = ReadBuffer(rel, 0); - LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); + seqh->tup = palloc0(HEAPTUPLESIZE); + + 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 seqh->tup */ + 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 @@ -1096,33 +1005,116 @@ 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(seqtuple->t_data, InvalidTransactionId); - seqtuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED; - seqtuple->t_data->t_infomask |= HEAP_XMAX_INVALID; - MarkBufferDirtyHint(*buf, true); + 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); } - seq = (Form_pg_sequence) GETSTRUCT(seqtuple); + seq_form = (Form_pg_sequence) GETSTRUCT(seqh->tup); /* this is a handy place to update our copy of the increment */ - elm->increment = seq->increment_by; + seqh->elm->increment = seq_form->increment_by; + + return seqh->tup; +} + +/* + * Update the page, optionally do WAL logging of the tuple + */ +void +sequence_save_tuple(SequenceHandle *seqh, HeapTuple newtup, bool do_wal) +{ + Page page; + + Assert(HeapTupleIsValid(seqh->tup)); + + page = BufferGetPage(seqh->buf); + + /* Only do tuple replacement if we got valid tuple on input. */ + if (HeapTupleIsValid(newtup)) + { + Page temppage; + + temppage = PageGetTempPageCopySpecial(page); + + if (PageAddItem(temppage, (Item) newtup->t_data, newtup->t_len, + FirstOffsetNumber, false, false) == InvalidOffsetNumber) + elog(PANIC, "sequence_save_tuple: failed to add item to page"); + + PageSetLSN(temppage, PageGetLSN(page)); + + START_CRIT_SECTION(); - return seq; + PageRestoreTempPage(temppage, page); + seqh->tup = newtup; + MarkBufferDirty(seqh->buf); + + if (do_wal) + log_sequence_tuple(seqh->rel, newtup, seqh->buf, page); + + END_CRIT_SECTION(); + } + else + { + START_CRIT_SECTION(); + + MarkBufferDirty(seqh->buf); + + if (do_wal) + log_sequence_tuple(seqh->rel, seqh->tup, seqh->buf, page); + + END_CRIT_SECTION(); + } +} + +void +sequence_release_tuple(SequenceHandle *seqh) +{ + /* Remove the tuple from cache */ + if (HeapTupleIsValid(seqh->tup)) + { + pfree(seqh->tup); + seqh->tup = NULL; + } + + /* Release the page lock */ + if (BufferIsValid(seqh->buf)) + { + UnlockReleaseBuffer(seqh->buf); + seqh->buf = InvalidBuffer; + } +} + +/* + * Returns true if sequence was not WAL logged since checkpoint + */ +bool +sequence_needs_wal(SequenceHandle *seqh) +{ + Page page; + XLogRecPtr redoptr = GetRedoRecPtr(); + + Assert(BufferIsValid(seqh->buf)); + + page = BufferGetPage(seqh->buf); + + 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 options with default values; - * otherwise, do not change existing options that aren't explicitly overridden. + * If isInit is true, fill any unspecified params with default values; + * otherwise, do not change existing params that aren't explicitly overridden. */ static void -init_params(List *options, bool isInit, +init_params(List *params, bool isInit, Form_pg_sequence new, List **owned_by) { DefElem *start_value = NULL; @@ -1132,13 +1124,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) { @@ -1209,13 +1201,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) { @@ -1224,7 +1209,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; @@ -1234,7 +1218,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; @@ -1243,7 +1226,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) { @@ -1251,14 +1233,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) { @@ -1266,7 +1246,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 */ @@ -1320,48 +1299,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) { @@ -1376,14 +1313,13 @@ 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; } /* - * Process an OWNED BY option for CREATE/ALTER SEQUENCE + * Process an OWNED BY param for CREATE/ALTER SEQUENCE * * Ownership permissions on the sequence are already checked, * but if we are establishing a new owned-by dependency, we must @@ -1399,8 +1335,7 @@ process_owned_by(Relation seqrel, List *owned_by) nnames = list_length(owned_by); Assert(nnames > 0); - if (nnames == 1) - { + if (nnames == 1) { /* Must be OWNED BY NONE */ if (strcmp(strVal(linitial(owned_by)), "none") != 0) ereport(ERROR, @@ -1487,20 +1422,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); + seqh = sequence_open(relid); 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", @@ -1518,7 +1450,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); @@ -1526,12 +1458,99 @@ 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_close(seqh); return HeapTupleGetDatum(heap_form_tuple(tupdesc, values, isnull)); } +Datum +pg_sequence_dump_state(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + char *result; + SequenceHandle *seqh; + + seqh = sequence_open(relid); + + result = seqam_dump_state(seqh->rel, seqh); + + sequence_close(seqh); + + PG_RETURN_CSTRING(result); +} + +Datum +pg_sequence_restore_state(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + char *state = PG_GETARG_CSTRING(1); + SequenceHandle *seqh; + + seqh = sequence_open(relid); + + seqam_restore_state(seqh->rel, seqh, state); + + sequence_close(seqh); + + PG_RETURN_VOID(); +} + +/* + * Update pg_class row for sequence to record change in relam. + * + * Call only while holding AccessExclusiveLock on sequence. + * + * Note that this is a transactional update of pg_class, rather + * than a non-transactional update of the tuple in the sequence's + * heap, as occurs elsewhere in this module. + */ +static void +seqrel_update_relam(Oid seqoid, Oid seqamid) +{ + Relation rd; + HeapTuple ctup; + Form_pg_class pgcform; + + rd = heap_open(RelationRelationId, RowExclusiveLock); + + /* Fetch a copy of the tuple to scribble on */ + ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(seqoid)); + if (!HeapTupleIsValid(ctup)) + elog(ERROR, "pg_class entry for sequence %u unavailable", + seqoid); + pgcform = (Form_pg_class) GETSTRUCT(ctup); + + if (pgcform->relam != seqamid) + { + pgcform->relam = seqamid; + simple_heap_update(rd, &ctup->t_self, ctup); + CatalogUpdateIndexes(rd, ctup); + } + + heap_freetuple(ctup); + heap_close(rd, RowExclusiveLock); + CommandCounterIncrement(); +} + +static void +log_sequence_tuple(Relation seqrel, HeapTuple 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) @@ -1583,6 +1602,7 @@ seq_redo(XLogReaderState *record) pfree(localpage); } + /* * Flush cached sequence information. */ @@ -1597,3 +1617,92 @@ ResetSequenceCaches(void) last_used_seq = NULL; } + +static Oid +get_new_seqam_oid(Oid oldAM, char *accessMethod) +{ + + if (oldAM && accessMethod == NULL) + return oldAM; + else if (accessMethod == NULL || strcmp(accessMethod, DEFAULT_SEQAM) == 0) + return GetDefaultSeqAM(); + else + return get_seqam_oid(accessMethod, false); +} + + +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; +} diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 1e737a0..f15c0c4 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -265,6 +265,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, @@ -2838,6 +2839,8 @@ AlterTableGetLockLevel(List *cmds) */ case AT_DropColumn: /* change visible to SELECT */ case AT_AddColumnToView: /* CREATE VIEW */ + case AT_AddColumnToSequence: /* ALTER SEQUENCE USING */ + case AT_DropColumnFromSequence: /* ALTER SEQUENCE USING */ case AT_DropOids: /* calls AT_DropColumn */ case AT_EnableAlwaysRule: /* may change SELECT rules */ case AT_EnableReplicaRule: /* may change SELECT rules */ @@ -3097,6 +3100,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* Recursion occurs during execution phase */ pass = AT_PASS_ADD_COL; break; + case AT_AddColumnToSequence: /* add column via ALTER SEQUENCE + USING */ + ATSimplePermissions(rel, ATT_SEQUENCE); + ATPrepAddColumn(wqueue, rel, recurse, recursing, cmd, lockmode); + /* This command never recurses */ + pass = AT_PASS_MISC; + break; case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */ /* @@ -3147,6 +3157,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* Recursion occurs during execution phase */ pass = AT_PASS_DROP; break; + case AT_DropColumnFromSequence: /* drop column via ALTER SEQUENCE + USING */ + ATSimplePermissions(rel, ATT_SEQUENCE); + ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd, lockmode); + /* This command never recurses */ + pass = AT_PASS_MISC; + break; case AT_AddIndex: /* ADD INDEX */ ATSimplePermissions(rel, ATT_TABLE); /* This command never recurses */ @@ -3399,6 +3416,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, case AT_AddColumn: /* ADD COLUMN */ case AT_AddColumnToView: /* add column via CREATE OR REPLACE * VIEW */ + case AT_AddColumnToSequence: /* add column via ALTER SEQUENCE + USING */ ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def, false, false, false, lockmode); break; @@ -3428,6 +3447,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, ATExecSetStorage(rel, cmd->name, cmd->def, lockmode); break; case AT_DropColumn: /* DROP COLUMN */ + case AT_DropColumnFromSequence: /* ALTER SEQUENCE USING */ ATExecDropColumn(wqueue, rel, cmd->name, cmd->behavior, false, false, cmd->missing_ok, lockmode); break; @@ -4211,6 +4231,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; @@ -4275,6 +4298,9 @@ ATWrongRelkindError(Relation rel, int allowed_targets) case ATT_FOREIGN_TABLE: msg = _("\"%s\" is not a foreign table"); break; + case ATT_SEQUENCE: + msg = _("\"%s\" is not a sequence"); + break; default: /* shouldn't get here, add all necessary cases above */ msg = _("\"%s\" is of the wrong type"); @@ -9046,6 +9072,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, case RELKIND_INDEX: (void) index_reloptions(rel->rd_am->amoptions, newOptions, true); break; + case RELKIND_SEQUENCE: + (void) sequence_reloptions(rel->rd_am->amoptions, newOptions, true); + break; default: ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 6b1bf7b..c9f7f8e 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3361,7 +3361,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; @@ -3374,7 +3376,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 d5db71d..90e765c 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1567,7 +1567,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; @@ -1578,7 +1580,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 4b5009b..56ff30b 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -51,6 +51,7 @@ #include "catalog/index.h" #include "catalog/namespace.h" +#include "catalog/pg_seqam.h" #include "catalog/pg_trigger.h" #include "commands/defrem.h" #include "commands/trigger.h" @@ -3503,7 +3504,33 @@ CreateSeqStmt: CreateSeqStmt *n = makeNode(CreateSeqStmt); $4->relpersistence = $2; n->sequence = $4; + n->accessMethod = DEFAULT_SEQAM; n->options = $5; + n->amoptions = NIL; + n->ownerId = InvalidOid; + $$ = (Node *)n; + } + | CREATE OptTemp SEQUENCE qualified_name OptSeqOptList + USING access_method + { + CreateSeqStmt *n = makeNode(CreateSeqStmt); + $4->relpersistence = $2; + n->sequence = $4; + n->accessMethod = $7; + n->options = $5; + n->amoptions = NIL; + n->ownerId = InvalidOid; + $$ = (Node *)n; + } + | CREATE OptTemp SEQUENCE qualified_name OptSeqOptList + USING access_method WITH reloptions + { + CreateSeqStmt *n = makeNode(CreateSeqStmt); + $4->relpersistence = $2; + n->sequence = $4; + n->accessMethod = $7; + n->options = $5; + n->amoptions = $9; n->ownerId = InvalidOid; n->if_not_exists = false; $$ = (Node *)n; @@ -3525,7 +3552,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; } @@ -3533,11 +3584,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; } @@ -3596,7 +3670,7 @@ SeqOptElem: CACHE NumericOnly { $$ = makeDefElem("restart", (Node *)$3); } - ; + ; opt_by: BY {} | /* empty */ {} diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 7c1939f..5994d18 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -399,6 +399,7 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) seqstmt = makeNode(CreateSeqStmt); seqstmt->sequence = makeRangeVar(snamespace, sname, -1); seqstmt->options = NIL; + seqstmt->amoptions = NIL; /* * If this is ALTER ADD COLUMN, make sure the sequence will be owned diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c index eca3f97..0aa4cf2 100644 --- a/src/backend/utils/cache/catcache.c +++ b/src/backend/utils/cache/catcache.c @@ -1059,10 +1059,12 @@ IndexScanOK(CatCache *cache, ScanKey cur_skey) case AMOID: case AMNAME: + case SEQAMOID: + case SEQAMNAME: /* - * Always do heap scans in pg_am, because it's so small there's - * not much point in an indexscan anyway. We *must* do this when + * Always do heap scans in pg_am and pg_seqam, because they are + * too small to benefit from an indexscan. We *must* do this when * initially building critical relcache entries, but we might as * well just always do it. */ diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 79244e5..d003a28 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -51,6 +51,7 @@ #include "catalog/pg_opclass.h" #include "catalog/pg_proc.h" #include "catalog/pg_rewrite.h" +#include "catalog/pg_seqam.h" #include "catalog/pg_tablespace.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" @@ -266,6 +267,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); @@ -1055,11 +1057,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); @@ -1535,6 +1540,39 @@ LookupOpclassInfo(Oid operatorClassOid, return opcentry; } +/* + * Initialize sequence-access-method support data for an index relation + */ +static void +RelationInitSequenceAccessInfo(Relation rel) +{ + HeapTuple amtuple; + MemoryContext indexcxt; + Form_pg_seqam amform; + + indexcxt = AllocSetContextCreate(CacheMemoryContext, + RelationGetRelationName(rel), + ALLOCSET_SMALL_MINSIZE, + ALLOCSET_SMALL_INITSIZE, + ALLOCSET_SMALL_MAXSIZE); + rel->rd_indexcxt = indexcxt; + + rel->rd_aminfo = (RelationAmInfo *) + MemoryContextAllocZero(rel->rd_indexcxt, + sizeof(RelationAmInfo)); + + /* + * Make a copy of the pg_am entry for the sequence's access method + */ + amtuple = SearchSysCache1(SEQAMOID, ObjectIdGetDatum(rel->rd_rel->relam)); + if (!HeapTupleIsValid(amtuple)) + elog(ERROR, "cache lookup failed for access method %u", + rel->rd_rel->relam); + amform = (Form_pg_seqam) MemoryContextAlloc(rel->rd_indexcxt, sizeof(*amform)); + memcpy(amform, GETSTRUCT(amtuple), sizeof(*amform)); + ReleaseSysCache(amtuple); + rel->rd_seqam = amform; +} /* * formrdesc @@ -4792,6 +4830,22 @@ load_relcache_init_file(bool shared) rel->rd_supportinfo = (FmgrInfo *) MemoryContextAllocZero(indexcxt, nsupport * sizeof(FmgrInfo)); } + else if (rel->rd_rel->relkind == RELKIND_SEQUENCE) + { + MemoryContext indexcxt; + Assert(!rel->rd_isnailed); + Assert(false); + + indexcxt = AllocSetContextCreate(CacheMemoryContext, + RelationGetRelationName(rel), + ALLOCSET_SMALL_MINSIZE, + ALLOCSET_SMALL_INITSIZE, + ALLOCSET_SMALL_MAXSIZE); + rel->rd_indexcxt = indexcxt; + /* set up zeroed fmgr-info vectors */ + rel->rd_aminfo = (RelationAmInfo *) + MemoryContextAllocZero(indexcxt, sizeof(RelationAmInfo)); + } else { /* Count nailed rels to ensure we have 'em all */ diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index 94d951c..66deaa6 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -54,6 +54,7 @@ #include "catalog/pg_shdepend.h" #include "catalog/pg_shdescription.h" #include "catalog/pg_shseclabel.h" +#include "catalog/pg_seqam.h" #include "catalog/pg_statistic.h" #include "catalog/pg_tablespace.h" #include "catalog/pg_ts_config.h" @@ -631,6 +632,28 @@ static const struct cachedesc cacheinfo[] = { }, 8 }, + {SeqAccessMethodRelationId, /* SEQAMNAME */ + SeqAmNameIndexId, + 1, + { + Anum_pg_seqam_amname, + 0, + 0, + 0 + }, + 4 + }, + {SeqAccessMethodRelationId, /* SEQAMOID */ + SeqAmOidIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 4 + }, {StatisticRelationId, /* STATRELATTINH */ StatisticRelidAttnumInhIndexId, 3, diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index b1bff7f..51c8982 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -28,6 +28,7 @@ #include "access/commit_ts.h" #include "access/gin.h" +#include "access/seqam.h" #include "access/transam.h" #include "access/twophase.h" #include "access/xact.h" @@ -2788,6 +2789,17 @@ static struct config_string ConfigureNamesString[] = }, { + {"default_sequenceam", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the default sequence am for any new sequences."), + gettext_noop("An empty string selects the 'local' sequence am."), + GUC_IS_NAME | GUC_NOT_IN_SAMPLE + }, + &default_seqam, + "", + check_default_seqam, NULL, NULL + }, + + { {"temp_tablespaces", PGC_USERSET, CLIENT_CONN_STATEMENT, gettext_noop("Sets the tablespace(s) to use for temporary tables and sort files."), NULL, diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 4175ddc..f4d10e0 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -14567,7 +14567,8 @@ dumpSequence(Archive *fout, DumpOptions *dopt, TableInfo *tbinfo) *incby, *maxv = NULL, *minv = NULL, - *cache; + *cache, + *amname = "local"; char bufm[100], bufx[100]; bool cycled; @@ -14637,15 +14638,41 @@ dumpSequence(Archive *fout, DumpOptions *dopt, TableInfo *tbinfo) } #endif - startv = PQgetvalue(res, 0, 1); - incby = PQgetvalue(res, 0, 2); + startv = pg_strdup(PQgetvalue(res, 0, 1)); + incby = pg_strdup(PQgetvalue(res, 0, 2)); if (!PQgetisnull(res, 0, 3)) - maxv = PQgetvalue(res, 0, 3); + maxv = pg_strdup(PQgetvalue(res, 0, 3)); if (!PQgetisnull(res, 0, 4)) - minv = PQgetvalue(res, 0, 4); - cache = PQgetvalue(res, 0, 5); + minv = pg_strdup(PQgetvalue(res, 0, 4)); + cache = pg_strdup(PQgetvalue(res, 0, 5)); cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0); + PQclear(res); + + res = ExecuteSqlQuery(fout, "SELECT EXISTS(SELECT 1 " + "FROM pg_catalog.pg_class c, " + "pg_catalog.pg_namespace n " + "WHERE n.oid = c.relnamespace " + "AND nspname = 'pg_catalog'" + "AND c.relname = 'pg_seqam' " + "AND c.relkind = 'r');", + PGRES_TUPLES_OK); + if (strcmp(PQgetvalue(res, 0, 0), "t") == 0) + { + PQclear(res); + + printfPQExpBuffer(query, "SELECT a.seqamname\n" + "FROM pg_catalog.pg_seqam a, pg_catalog.pg_class c\n" + "WHERE c.relam = a.oid AND c.oid = %u", + tbinfo->dobj.catId.oid); + + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + + amname = pg_strdup(PQgetvalue(res, 0, 0)); + } + + PQclear(res); + /* * DROP must be fully qualified in case same name appears in pg_catalog */ @@ -14687,6 +14714,7 @@ dumpSequence(Archive *fout, DumpOptions *dopt, TableInfo *tbinfo) " CACHE %s%s", cache, (cycled ? "\n CYCLE" : "")); + appendPQExpBuffer(query, "\n USING %s", fmtId(amname)); appendPQExpBufferStr(query, ";\n"); appendPQExpBuffer(labelq, "SEQUENCE %s", fmtId(tbinfo->dobj.name)); @@ -14753,8 +14781,6 @@ dumpSequence(Archive *fout, DumpOptions *dopt, TableInfo *tbinfo) tbinfo->dobj.namespace->dobj.name, tbinfo->rolname, tbinfo->dobj.catId, 0, tbinfo->dobj.dumpId); - PQclear(res); - destroyPQExpBuffer(query); destroyPQExpBuffer(delqry); destroyPQExpBuffer(labelq); @@ -14769,17 +14795,30 @@ dumpSequenceData(Archive *fout, TableDataInfo *tdinfo) { TableInfo *tbinfo = tdinfo->tdtable; PGresult *res; - char *last; - bool called; + char *state; 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)); + res = ExecuteSqlQuery(fout, "SELECT EXISTS(SELECT 1 " + "FROM pg_catalog.pg_proc p, " + "pg_catalog.pg_namespace n " + "WHERE n.oid = p.pronamespace " + "AND nspname = 'pg_catalog'" + "AND p.proname = 'pg_sequence_dump_state');", + PGRES_TUPLES_OK); + + if (strcmp(PQgetvalue(res, 0, 0), "t") == 0) + appendPQExpBuffer(query, + "SELECT quote_literal(last_value::text || ', ' || is_called::text) FROM %s", + fmtId(tbinfo->dobj.name)); + else + appendPQExpBuffer(query, + "SELECT quote_literal(pg_catalog.pg_sequence_dump_state(%s))", + fmtId(tbinfo->dobj.name)); + PQclear(res); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); if (PQntuples(res) != 1) @@ -14791,14 +14830,12 @@ dumpSequenceData(Archive *fout, TableDataInfo *tdinfo) exit_nicely(1); } - last = PQgetvalue(res, 0, 0); - called = (strcmp(PQgetvalue(res, 0, 1), "t") == 0); + state = PQgetvalue(res, 0, 0); resetPQExpBuffer(query); - appendPQExpBufferStr(query, "SELECT pg_catalog.setval("); + appendPQExpBufferStr(query, "SELECT pg_catalog.pg_sequence_restore_state("); appendStringLiteralAH(query, fmtId(tbinfo->dobj.name), fout); - appendPQExpBuffer(query, ", %s, %s);\n", - last, (called ? "true" : "false")); + appendPQExpBuffer(query, ", %s);\n", state); ArchiveEntry(fout, nilCatalogId, createDumpId(), tbinfo->dobj.name, diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 5a9ceca..6dc428d 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -1372,30 +1372,6 @@ describeOneTableDetails(const char *schemaname, res = NULL; /* - * If it's a sequence, fetch its values and store into an array that will - * be used later. - */ - if (tableinfo.relkind == 'S') - { - printfPQExpBuffer(&buf, "SELECT * FROM %s", fmtId(schemaname)); - /* must be separate because fmtId isn't reentrant */ - appendPQExpBuffer(&buf, ".%s;", fmtId(relationname)); - - res = PSQLexec(buf.data); - if (!res) - goto error_return; - - seq_values = pg_malloc((PQnfields(res) + 1) * sizeof(*seq_values)); - - for (i = 0; i < PQnfields(res); i++) - seq_values[i] = pg_strdup(PQgetvalue(res, 0, i)); - seq_values[i] = NULL; - - PQclear(res); - res = NULL; - } - - /* * Get column info * * You need to modify value of "firstvcol" which will be defined below if @@ -1439,6 +1415,14 @@ 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' && !verbose) + appendPQExpBufferStr(&buf, " AND attname IN ('sequence_name', " + "'start_value', 'increment_by', 'max_value', " + "'min_value', 'cache_value', 'is_cycled')"); appendPQExpBufferStr(&buf, "\nORDER BY a.attnum;"); res = PSQLexec(buf.data); @@ -1446,6 +1430,39 @@ describeOneTableDetails(const char *schemaname, goto error_return; numrows = PQntuples(res); + /* + * If it's a sequence, fetch its values and store into an array that will + * be used later. + */ + if (tableinfo.relkind == 'S') + { + PGresult *result; + + /* + * Use column names from the column info query, to automatically skip + * unwanted columns. + */ + printfPQExpBuffer(&buf, "SELECT "); + for (i = 0; i < numrows; i++) + appendPQExpBuffer(&buf, i > 0 ? ", %s" : "%s", fmtId(PQgetvalue(res, i, 0))); + appendPQExpBuffer(&buf, " FROM %s", + fmtId(schemaname)); + /* must be separate because fmtId isn't reentrant */ + appendPQExpBuffer(&buf, ".%s;", fmtId(relationname)); + + result = PSQLexec(buf.data); + if (!result) + goto error_return; + + seq_values = pg_malloc((PQnfields(result) + 1) * sizeof(*seq_values)); + + for (i = 0; i < PQnfields(result); i++) + seq_values[i] = pg_strdup(PQgetvalue(result, 0, i)); + seq_values[i] = NULL; + + PQclear(result); + } + /* Make title */ switch (tableinfo.relkind) { @@ -1757,6 +1774,29 @@ describeOneTableDetails(const char *schemaname, /* Footer information about a sequence */ PGresult *result = NULL; + /* Get the Access Method name for the sequence */ + printfPQExpBuffer(&buf, "SELECT a.seqamname\n" + "FROM pg_catalog.pg_seqam a, pg_catalog.pg_class c\n" + "WHERE c.relam = a.oid AND c.oid = %s", oid); + + result = PSQLexec(buf.data); + + /* + * If we get no rows back, don't show anything (obviously). We should + * never get more than one row back, but if we do, just ignore it and + * don't print anything. + */ + if (!result) + goto error_return; + else if (PQntuples(result) == 1) + { + printfPQExpBuffer(&buf, _("Access Method: %s"), + PQgetvalue(result, 0, 0)); + printTableAddFooter(&cont, buf.data); + } + + PQclear(result); + /* Get the column that owns this sequence */ printfPQExpBuffer(&buf, "SELECT pg_catalog.quote_ident(nspname) || '.' ||" "\n pg_catalog.quote_ident(relname) || '.' ||" @@ -1774,6 +1814,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) @@ -1783,11 +1825,6 @@ describeOneTableDetails(const char *schemaname, printTableAddFooter(&cont, buf.data); } - /* - * If we get no rows back, don't show anything (obviously). We should - * never get more than one row back, but if we do, just ignore it and - * don't print anything. - */ PQclear(result); } else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' || diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a538830..bf4cafa 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -46,8 +46,9 @@ typedef enum relopt_kind RELOPT_KIND_SPGIST = (1 << 8), RELOPT_KIND_VIEW = (1 << 9), RELOPT_KIND_BRIN = (1 << 10), + RELOPT_KIND_SEQUENCE = (1 << 11), /* if you add a new kind, make sure you update "last_default" too */ - RELOPT_KIND_LAST_DEFAULT = RELOPT_KIND_BRIN, + RELOPT_KIND_LAST_DEFAULT = RELOPT_KIND_SEQUENCE, /* some compilers treat enums as signed ints, so we can't use 1 << 31 */ RELOPT_KIND_MAX = (1 << 30) } relopt_kind; @@ -272,6 +273,8 @@ extern bytea *heap_reloptions(char relkind, Datum reloptions, bool validate); extern bytea *view_reloptions(Datum reloptions, bool validate); extern bytea *index_reloptions(RegProcedure amoptions, Datum reloptions, bool validate); +extern bytea *sequence_reloptions(RegProcedure amoptions, Datum reloptions, + bool validate); extern bytea *attribute_reloptions(Datum reloptions, bool validate); extern bytea *tablespace_reloptions(Datum reloptions, bool validate); diff --git a/src/include/access/seqam.h b/src/include/access/seqam.h new file mode 100644 index 0000000..c4f6f73 --- /dev/null +++ b/src/include/access/seqam.h @@ -0,0 +1,75 @@ +/*------------------------------------------------------------------------- + * + * seqam.h + * Public header file for Sequence access method. + * + * + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/access/seqam.h + * + *------------------------------------------------------------------------- + */ +#ifndef SEQAM_H +#define SEQAM_H + +#include "fmgr.h" + +#include "access/htup.h" +#include "commands/sequence.h" +#include "utils/relcache.h" +#include "storage/buf.h" +#include "storage/bufpage.h" + + +struct SequenceHandle; +typedef struct SequenceHandle SequenceHandle; + +typedef struct SeqAMColumnData +{ + char *colname; + Oid coltyp; + int32 coltypmod; + bool colnotnull; +} SeqAMColumnData; + +typedef SeqAMColumnData *SeqAMColumn; + +extern char *default_seqam; + +extern Oid GetDefaultSeqAM(void); + +extern List *seqam_extra_columns(Oid seqamid); +extern void seqam_init(Oid seqamid, List *seqparams, + List *reloptions, bool is_init, + Datum *values, bool *nulls); +extern int64 seqam_alloc(Relation seqrel, SequenceHandle *seqh, + int64 nrequested, int64 *last); +extern void seqam_setval(Relation seqrel, SequenceHandle *seqh, + int64 new_value); +extern char *seqam_dump_state(Relation seqrel, SequenceHandle *seqh); +extern void seqam_restore_state(Relation seqrel, SequenceHandle *seqh, char *state); + +extern Oid get_seqam_oid(const char *sequencename, bool missing_ok); + +extern SequenceHandle *sequence_open(Oid seq_relid); +extern void sequence_close(SequenceHandle *seqh); +extern HeapTuple sequence_read_tuple(SequenceHandle *seqh); +extern void sequence_save_tuple(SequenceHandle *seqh, HeapTuple newtup, bool do_wal); +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 Datum seqam_local_extracols(PG_FUNCTION_ARGS); +extern Datum seqam_local_reloptions(PG_FUNCTION_ARGS); +extern Datum seqam_local_init(PG_FUNCTION_ARGS); +extern Datum seqam_local_alloc(PG_FUNCTION_ARGS); +extern Datum seqam_local_setval(PG_FUNCTION_ARGS); +extern Datum seqam_local_dump(PG_FUNCTION_ARGS); +extern Datum seqam_local_restore(PG_FUNCTION_ARGS); + +#endif /* SEQAM_H */ diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index bde1a84..2daffe4 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -206,6 +206,11 @@ DECLARE_UNIQUE_INDEX(pg_rewrite_oid_index, 2692, on pg_rewrite using btree(oid o DECLARE_UNIQUE_INDEX(pg_rewrite_rel_rulename_index, 2693, on pg_rewrite using btree(ev_class oid_ops, rulename name_ops)); #define RewriteRelRulenameIndexId 2693 +DECLARE_UNIQUE_INDEX(pg_seqam_name_index, 6020, on pg_seqam using btree(seqamname name_ops)); +#define SeqAmNameIndexId 6020 +DECLARE_UNIQUE_INDEX(pg_seqam_oid_index, 6021, on pg_seqam using btree(oid oid_ops)); +#define SeqAmOidIndexId 6021 + DECLARE_INDEX(pg_shdepend_depender_index, 1232, on pg_shdepend using btree(dbid oid_ops, classid oid_ops, objid oid_ops, objsubid int4_ops)); #define SharedDependDependerIndexId 1232 DECLARE_INDEX(pg_shdepend_reference_index, 1233, on pg_shdepend using btree(refclassid oid_ops, refobjid oid_ops)); diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 910cfc6..a96347f 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -1814,10 +1814,12 @@ DATA(insert OID = 1575 ( currval PGNSP PGUID 12 1 0 0 0 f f f f t f v 1 0 20 DESCR("sequence current value"); DATA(insert OID = 1576 ( setval PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 20 "2205 20" _null_ _null_ _null_ _null_ setval_oid _null_ _null_ _null_ )); DESCR("set sequence value"); -DATA(insert OID = 1765 ( setval PGNSP PGUID 12 1 0 0 0 f f f f t f v 3 0 20 "2205 20 16" _null_ _null_ _null_ _null_ setval3_oid _null_ _null_ _null_ )); -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 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_ pg_sequence_parameters _null_ _null_ _null_)); DESCR("sequence parameters, for use by information schema"); +DATA(insert OID = 3261 ( pg_sequence_dump_state PGNSP PGUID 12 1 0 0 0 f f f f t f v 1 0 2275 "2205" _null_ _null_ _null_ _null_ pg_sequence_dump_state _null_ _null_ _null_ )); +DESCR("Dump state of a sequence"); +DATA(insert OID = 3262 ( pg_sequence_restore_state PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 2278 "2205 2275" _null_ _null_ _null_ _null_ pg_sequence_restore_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 3 0 1562 "2275 26 23" _null_ _null_ _null_ _null_ varbit_in _null_ _null_ _null_ )); DESCR("I/O"); @@ -5042,6 +5044,22 @@ DESCR("peek at changes from replication slot"); DATA(insert OID = 3785 ( pg_logical_slot_peek_binary_changes PGNSP PGUID 12 1000 1000 25 0 f f f f f t v 4 0 2249 "19 3220 23 1009" "{19,3220,23,1009,3220,28,17}" "{i,i,i,v,o,o,o}" "{slot_name,upto_lsn,upto_nchanges,options,location,xid,data}" _null_ pg_logical_slot_peek_binary_changes _null_ _null_ _null_ )); DESCR("peek at binary changes from replication slot"); +DATA(insert OID = 6022 ( seqam_local_extracols PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2281 "2281" _null_ _null_ _null_ _null_ seqam_local_extracols _null_ _null_ _null_ )); +DESCR("Get local SequenceAM extra columns"); +DATA(insert OID = 6023 ( seqam_local_reloptions PGNSP PGUID 12 1 0 0 0 f f f f f f s 2 0 2281 "2281 16" _null_ _null_ _null_ _null_ seqam_local_reloptions _null_ _null_ _null_ )); +DESCR("Local SequenceAM options"); +DATA(insert OID = 6024 ( seqam_local_init PGNSP PGUID 12 1 0 0 0 f f f f f f s 5 0 2278 "2281 17 16 2281 2281" _null_ _null_ _null_ _null_ seqam_local_init _null_ _null_ _null_ )); +DESCR("Initialize local SequenceAM sequence"); +DATA(insert OID = 6025 ( seqam_local_alloc PGNSP PGUID 12 1 0 0 0 f f f f f f v 4 0 2281 "2281 2281 20 2281" _null_ _null_ _null_ _null_ seqam_local_alloc _null_ _null_ _null_ )); +DESCR("Local SequenceAM allocation"); +DATA(insert OID = 6026 ( seqam_local_setval PGNSP PGUID 12 1 0 0 0 f f f f f f s 3 0 2278 "2281 2281 20" _null_ _null_ _null_ _null_ seqam_local_setval _null_ _null_ _null_ )); +DESCR("Set value of a local SequenceAM sequence"); +DATA(insert OID = 6027 ( seqam_local_dump PGNSP PGUID 12 1 0 0 0 f f f f f f s 2 0 17 "2281 2281" _null_ _null_ _null_ _null_ seqam_local_dump _null_ _null_ _null_ )); +DESCR("Dump state of a local SequenceAM sequence"); +DATA(insert OID = 6028 ( seqam_local_restore PGNSP PGUID 12 1 0 0 0 f f f f f f s 3 0 2278 "2281 2281 17" _null_ _null_ _null_ _null_ seqam_local_restore _null_ _null_ _null_ )); +DESCR("Restore state of a local SequenceAM sequence"); + + /* event triggers */ DATA(insert OID = 3566 ( pg_event_trigger_dropped_objects PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25}" "{o,o,o,o,o,o,o}" "{classid, objid, objsubid, object_type, schema_name, object_name, object_identity}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ )); DESCR("list objects dropped by the current command"); diff --git a/src/include/catalog/pg_seqam.h b/src/include/catalog/pg_seqam.h new file mode 100644 index 0000000..e08c935 --- /dev/null +++ b/src/include/catalog/pg_seqam.h @@ -0,0 +1,78 @@ +/*------------------------------------------------------------------------- + * + * pg_seqam.h + * definition of the system "sequence access method" relation (pg_seqam) + * along with the relation's initial contents. + * + * + * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_seqam.h + * + * NOTES + * the genbki.pl script reads this file and generates .bki + * information from the DATA() statements. + * + * XXX do NOT break up DATA() statements into multiple lines! + * the scripts are not as smart as you might think... + * + *------------------------------------------------------------------------- + */ +#ifndef PG_SEQAM_H +#define PG_SEQAM_H + +#include "catalog/genbki.h" + +/* ---------------- + * pg_seqam definition. cpp turns this into + * typedef struct FormData_pg_seqam + * ---------------- + */ +#define SeqAccessMethodRelationId 32 + +CATALOG(pg_seqam,32) +{ + NameData seqamname; /* access method name */ + regproc seqamextracols; /* get extra columns this am stores in the sequence relation */ + regproc seqamreloptions; /* parse AM-specific options */ + regproc seqaminit; /* sequence initialization */ + regproc seqamalloc; /* get next allocation of range of values function */ + regproc seqamsetval; /* set value function */ + regproc seqamdump; /* dump state, used by pg_dump */ + regproc seqamrestore; /* restore state, used when loading pg_dump */ +} FormData_pg_seqam; + +/* ---------------- + * Form_pg_seqam corresponds to a pointer to a tuple with + * the format of pg_seqam relation. + * ---------------- + */ +typedef FormData_pg_seqam *Form_pg_seqam; + +/* ---------------- + * compiler constants for pg_seqam + * ---------------- + */ +#define Natts_pg_seqam 8 +#define Anum_pg_seqam_amname 1 +#define Anum_pg_seqam_amextracols 2 +#define Anum_pg_seqam_amreloptions 3 +#define Anum_pg_seqam_aminit 4 +#define Anum_pg_seqam_amalloc 5 +#define Anum_pg_seqam_amsetval 6 +#define Anum_pg_seqam_amdump 7 +#define Anum_pg_seqam_amrestore 8 + +/* ---------------- + * initial contents of pg_seqam + * ---------------- + */ + +DATA(insert OID = 2 ( local seqam_local_extracols seqam_local_reloptions seqam_local_init seqam_local_alloc seqam_local_setval seqam_local_dump seqam_local_restore)); +DESCR("local sequence access method"); +#define LOCAL_SEQAM_OID 2 + +#define DEFAULT_SEQAM "" + +#endif /* PG_SEQAM_H */ diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h index 386f1e6..8688620 100644 --- a/src/include/commands/sequence.h +++ b/src/include/commands/sequence.h @@ -23,15 +23,12 @@ 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; } FormData_pg_sequence; typedef FormData_pg_sequence *Form_pg_sequence; @@ -41,18 +38,14 @@ typedef FormData_pg_sequence *Form_pg_sequence; */ #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 2 +#define SEQ_COL_INCBY 3 +#define SEQ_COL_MAXVALUE 4 +#define SEQ_COL_MINVALUE 5 +#define SEQ_COL_CACHE 6 +#define SEQ_COL_CYCLE 7 #define SEQ_COL_FIRSTCOL SEQ_COL_NAME -#define SEQ_COL_LASTCOL SEQ_COL_CALLED +#define SEQ_COL_LASTCOL SEQ_COL_CYCLE /* XLOG stuff */ #define XLOG_SEQ_LOG 0x00 @@ -67,10 +60,11 @@ extern Datum nextval(PG_FUNCTION_ARGS); extern Datum nextval_oid(PG_FUNCTION_ARGS); extern Datum currval_oid(PG_FUNCTION_ARGS); extern Datum setval_oid(PG_FUNCTION_ARGS); -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_dump_state(PG_FUNCTION_ARGS); +extern Datum pg_sequence_restore_state(PG_FUNCTION_ARGS); extern Oid DefineSequence(CreateSeqStmt *stmt); extern Oid AlterSequence(AlterSeqStmt *stmt); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 5eaa435..fb355dc 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1283,6 +1283,7 @@ typedef enum AlterTableType AT_AddColumn, /* add column */ AT_AddColumnRecurse, /* internal to commands/tablecmds.c */ AT_AddColumnToView, /* implicitly via CREATE OR REPLACE VIEW */ + AT_AddColumnToSequence, /* implicitly via ALTER SEQUENCE USING */ AT_ColumnDefault, /* alter column default */ AT_DropNotNull, /* alter column drop not null */ AT_SetNotNull, /* alter column set not null */ @@ -1292,6 +1293,7 @@ typedef enum AlterTableType AT_SetStorage, /* alter column set storage */ AT_DropColumn, /* drop column */ AT_DropColumnRecurse, /* internal to commands/tablecmds.c */ + AT_DropColumnFromSequence, /* implicitly via ALTER SEQUENCE USING */ AT_AddIndex, /* add index */ AT_ReAddIndex, /* internal to commands/tablecmds.c */ AT_AddConstraint, /* add constraint */ @@ -2011,8 +2013,10 @@ typedef struct CreateSeqStmt { NodeTag type; RangeVar *sequence; /* the sequence to create */ - List *options; + List *options; /* standard sequence options */ + List *amoptions; /* am specific options */ Oid ownerId; /* ID of owner, or InvalidOid for default */ + char *accessMethod; /* USING name of access method (eg. Local) */ bool if_not_exists; /* just do nothing if it already exists? */ } CreateSeqStmt; @@ -2020,8 +2024,10 @@ typedef struct AlterSeqStmt { NodeTag type; RangeVar *sequence; /* the sequence to alter */ - List *options; + List *options; /* standard sequence options */ + List *amoptions; /* am specific options */ bool missing_ok; /* skip error if a role is missing? */ + char *accessMethod; /* USING name of access method (eg. Local) */ } AlterSeqStmt; /* ---------------------- diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index 80813d2..507044e 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -404,6 +404,7 @@ extern void GUC_check_errcode(int sqlerrcode); */ /* in commands/tablespace.c */ +extern bool check_default_seqam(char **newval, void **extra, GucSource source); extern bool check_default_tablespace(char **newval, void **extra, GucSource source); extern bool check_temp_tablespaces(char **newval, void **extra, GucSource source); extern void assign_temp_tablespaces(const char *newval, void *extra); diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 48ebf59..2998688 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -18,6 +18,7 @@ #include "catalog/pg_am.h" #include "catalog/pg_class.h" #include "catalog/pg_index.h" +#include "catalog/pg_seqam.h" #include "fmgr.h" #include "nodes/bitmapset.h" #include "rewrite/prs2lock.h" @@ -48,10 +49,12 @@ typedef LockInfoData *LockInfo; /* * Cached lookup information for the frequently used index access method - * functions, defined by the pg_am row associated with an index relation. + * functions, defined by the pg_am row associated with an index relation, or the pg_seqam + * row associated with a sequence relation. */ typedef struct RelationAmInfo { + /* pg_am only */ FmgrInfo aminsert; FmgrInfo ambeginscan; FmgrInfo amgettuple; @@ -61,6 +64,14 @@ typedef struct RelationAmInfo FmgrInfo ammarkpos; FmgrInfo amrestrpos; FmgrInfo amcanreturn; + FmgrInfo amcostestimate; + + /* pg_seqam only */ + FmgrInfo seqamalloc; + FmgrInfo seqamsetval; + + /* Common */ + FmgrInfo amoptions; } RelationAmInfo; @@ -131,23 +142,25 @@ typedef struct RelationData struct HeapTupleData *rd_indextuple; /* all of pg_index tuple */ Form_pg_am rd_am; /* pg_am tuple for index's AM */ + Form_pg_seqam rd_seqam; /* pg_seqam tuple for sequence's AM */ + /* - * index access support info (used only for an index relation) + * Access support info (used only for index or sequence relations) * * Note: only default support procs for each opclass are cached, namely * those with lefttype and righttype equal to the opclass's opcintype. The * arrays are indexed by support function number, which is a sufficient * identifier given that restriction. * - * Note: rd_amcache is available for index AMs to cache private data about - * an index. This must be just a cache since it may get reset at any time + * Note: rd_amcache is available for AMs to cache private data about + * an object. This must be just a cache since it may get reset at any time * (in particular, it will get reset by a relcache inval message for the * index). If used, it must point to a single memory chunk palloc'd in * rd_indexcxt. A relcache reset will include freeing that chunk and * setting rd_amcache = NULL. */ MemoryContext rd_indexcxt; /* private memory cxt for this stuff */ - RelationAmInfo *rd_aminfo; /* lookup info for funcs found in pg_am */ + RelationAmInfo *rd_aminfo; /* lookup info for funcs found in pg_am or pg_seqam */ Oid *rd_opfamily; /* OIDs of op families for each index col */ Oid *rd_opcintype; /* OIDs of opclass declared input data types */ RegProcedure *rd_support; /* OIDs of support procedures */ @@ -158,7 +171,7 @@ typedef struct RelationData Oid *rd_exclops; /* OIDs of exclusion operators, if any */ Oid *rd_exclprocs; /* OIDs of exclusion ops' procs, if any */ uint16 *rd_exclstrats; /* exclusion ops' strategy numbers, if any */ - void *rd_amcache; /* available for use by index AM */ + void *rd_amcache; /* available for use by AM */ Oid *rd_indcollation; /* OIDs of index collations */ /* diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h index f97229f..2810a8d 100644 --- a/src/include/utils/syscache.h +++ b/src/include/utils/syscache.h @@ -78,6 +78,8 @@ enum SysCacheIdentifier RELNAMENSP, RELOID, RULERELNAME, + SEQAMNAME, + SEQAMOID, STATRELATTINH, TABLESPACEOID, TSCONFIGMAP, diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out index 7991e99..6b6493e 100644 --- a/src/test/regress/expected/rangefuncs.out +++ b/src/test/regress/expected/rangefuncs.out @@ -719,10 +719,10 @@ CREATE FUNCTION foo_mat(int,int) RETURNS setof foo_rescan_t AS 'begin for i in $ --invokes ExecReScanFunctionScan - all these cases should materialize the function only once -- LEFT JOIN on a condition that the planner can't prove to be true is used to ensure the function -- is on the inner path of a nestloop join -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); + pg_sequence_restore_state | pg_sequence_restore_state +---------------------------+--------------------------- + | (1 row) SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) ON (r+i)<100; @@ -739,10 +739,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) ON (r+i)<100; 3 | 13 | 3 (9 rows) -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); + pg_sequence_restore_state | pg_sequence_restore_state +---------------------------+--------------------------- + | (1 row) SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100; @@ -759,10 +759,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) WITH ORDINALITY 3 | 13 | 3 | 3 (9 rows) -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); + pg_sequence_restore_state | pg_sequence_restore_state +---------------------------+--------------------------- + | (1 row) SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) ON (r+i)<100; @@ -779,10 +779,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) ON (r+i)<100; 3 | 13 | 3 (9 rows) -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); + pg_sequence_restore_state | pg_sequence_restore_state +---------------------------+--------------------------- + | (1 row) SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100; @@ -799,10 +799,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) WITH ORDINALITY 3 | 13 | 3 | 3 (9 rows) -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); + pg_sequence_restore_state | pg_sequence_restore_state +---------------------------+--------------------------- + | (1 row) SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN ROWS FROM( foo_sql(11,13), foo_mat(11,13) ) WITH ORDINALITY AS f(i1,s1,i2,s2,o) ON (r+i1+i2)<100; @@ -876,10 +876,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) WITH O (9 rows) --invokes ExecReScanFunctionScan with chgParam != NULL (using implied LATERAL) -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); + pg_sequence_restore_state | pg_sequence_restore_state +---------------------------+--------------------------- + | (1 row) SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13); @@ -893,10 +893,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13); 3 | 13 | 6 (6 rows) -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); + pg_sequence_restore_state | pg_sequence_restore_state +---------------------------+--------------------------- + | (1 row) SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13) WITH ORDINALITY AS f(i,s,o); @@ -910,10 +910,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13) WITH ORDINALITY AS f(i 3 | 13 | 6 | 1 (6 rows) -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); + pg_sequence_restore_state | pg_sequence_restore_state +---------------------------+--------------------------- + | (1 row) SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r); @@ -927,10 +927,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r); 3 | 13 | 6 (6 rows) -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); + pg_sequence_restore_state | pg_sequence_restore_state +---------------------------+--------------------------- + | (1 row) SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r) WITH ORDINALITY AS f(i,s,o); @@ -944,10 +944,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r) WITH ORDINALITY AS f(i 3 | 13 | 6 | 3 (6 rows) -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); + pg_sequence_restore_state | pg_sequence_restore_state +---------------------------+--------------------------- + | (1 row) SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2); @@ -965,10 +965,10 @@ SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2); 16 | 20 | 20 | 10 (10 rows) -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); + pg_sequence_restore_state | pg_sequence_restore_state +---------------------------+--------------------------- + | (1 row) SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2) WITH ORDINALITY AS f(i,s,o); @@ -986,10 +986,10 @@ SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2) WITH ORD 16 | 20 | 20 | 10 | 5 (10 rows) -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); + pg_sequence_restore_state | pg_sequence_restore_state +---------------------------+--------------------------- + | (1 row) SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13); @@ -1003,10 +1003,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13); 3 | 13 | 6 (6 rows) -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); + pg_sequence_restore_state | pg_sequence_restore_state +---------------------------+--------------------------- + | (1 row) SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13) WITH ORDINALITY AS f(i,s,o); @@ -1020,10 +1020,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13) WITH ORDINALITY AS f(i 3 | 13 | 6 | 1 (6 rows) -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); + pg_sequence_restore_state | pg_sequence_restore_state +---------------------------+--------------------------- + | (1 row) SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r); @@ -1037,10 +1037,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r); 3 | 13 | 6 (6 rows) -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); + pg_sequence_restore_state | pg_sequence_restore_state +---------------------------+--------------------------- + | (1 row) SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r) WITH ORDINALITY AS f(i,s,o); @@ -1054,10 +1054,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r) WITH ORDINALITY AS f(i 3 | 13 | 6 | 3 (6 rows) -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); + pg_sequence_restore_state | pg_sequence_restore_state +---------------------------+--------------------------- + | (1 row) SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2); @@ -1075,10 +1075,10 @@ SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2); 16 | 20 | 20 | 10 (10 rows) -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); + pg_sequence_restore_state | pg_sequence_restore_state +---------------------------+--------------------------- + | (1 row) SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2) WITH ORDINALITY AS f(i,s,o); @@ -1097,10 +1097,10 @@ SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2) WITH ORD (10 rows) -- selective rescan of multiple functions: -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); + pg_sequence_restore_state | pg_sequence_restore_state +---------------------------+--------------------------- + | (1 row) SELECT * FROM (VALUES (1),(2),(3)) v(r), ROWS FROM( foo_sql(11,11), foo_mat(10+r,13) ); @@ -1114,10 +1114,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r), ROWS FROM( foo_sql(11,11), foo_mat(10+r 3 | 11 | 1 | 13 | 6 (6 rows) -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); + pg_sequence_restore_state | pg_sequence_restore_state +---------------------------+--------------------------- + | (1 row) SELECT * FROM (VALUES (1),(2),(3)) v(r), ROWS FROM( foo_sql(10+r,13), foo_mat(11,11) ); @@ -1131,10 +1131,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r), ROWS FROM( foo_sql(10+r,13), foo_mat(11 3 | 13 | 6 | 11 | 1 (6 rows) -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); + pg_sequence_restore_state | pg_sequence_restore_state +---------------------------+--------------------------- + | (1 row) SELECT * FROM (VALUES (1),(2),(3)) v(r), ROWS FROM( foo_sql(10+r,13), foo_mat(10+r,13) ); @@ -1148,10 +1148,10 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r), ROWS FROM( foo_sql(10+r,13), foo_mat(10 3 | 13 | 6 | 13 | 6 (6 rows) -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); - setval | setval ---------+-------- - 1 | 1 +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); + pg_sequence_restore_state | pg_sequence_restore_state +---------------------------+--------------------------- + | (1 row) SELECT * FROM generate_series(1,2) r1, generate_series(r1,3) r2, ROWS FROM( foo_sql(10+r1,13), foo_mat(10+r2,13) ); diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index c7be273..d4e250f 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -123,6 +123,7 @@ pg_proc|t pg_range|t pg_rewrite|t pg_seclabel|t +pg_seqam|t pg_shdepend|t pg_shdescription|t pg_shseclabel|t diff --git a/src/test/regress/expected/sequence.out b/src/test/regress/expected/sequence.out index 8783ca6..ea45a2d 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_restore_state('sequence_test'::text, '99, false'); + pg_sequence_restore_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_restore_state('sequence_test'::regclass, '99, false'); + pg_sequence_restore_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 + sequence_name | start_value | increment_by | max_value | min_value | cache_value | is_cycled | last_value | log_cnt | is_called +---------------+-------------+--------------+---------------------+-----------+-------------+-----------+------------+---------+----------- + foo_seq | 1 | 1 | 9223372036854775807 | 1 | 1 | f | 1 | 0 | f (1 row) SELECT nextval('foo_seq_new'); @@ -191,9 +191,9 @@ SELECT nextval('foo_seq_new'); (1 row) SELECT * FROM foo_seq_new; - sequence_name | last_value | start_value | increment_by | max_value | min_value | cache_value | log_cnt | is_cycled | is_called ----------------+------------+-------------+--------------+---------------------+-----------+-------------+---------+-----------+----------- - foo_seq | 2 | 1 | 1 | 9223372036854775807 | 1 | 1 | 31 | f | t + sequence_name | start_value | increment_by | max_value | min_value | cache_value | is_cycled | last_value | log_cnt | is_called +---------------+-------------+--------------+---------------------+-----------+-------------+-----------+------------+---------+----------- + foo_seq | 1 | 1 | 9223372036854775807 | 1 | 1 | f | 2 | 31 | t (1 row) DROP SEQUENCE foo_seq_new; diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out index 80c5706..d1bc3d7 100644 --- a/src/test/regress/expected/updatable_views.out +++ b/src/test/regress/expected/updatable_views.out @@ -100,14 +100,14 @@ SELECT table_name, column_name, is_updatable 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 | last_value | NO + ro_view19 | log_cnt | NO ro_view19 | is_called | NO ro_view2 | a | NO ro_view2 | b | NO diff --git a/src/test/regress/sql/rangefuncs.sql b/src/test/regress/sql/rangefuncs.sql index 470571b..b97670f 100644 --- a/src/test/regress/sql/rangefuncs.sql +++ b/src/test/regress/sql/rangefuncs.sql @@ -242,16 +242,16 @@ CREATE FUNCTION foo_mat(int,int) RETURNS setof foo_rescan_t AS 'begin for i in $ -- LEFT JOIN on a condition that the planner can't prove to be true is used to ensure the function -- is on the inner path of a nestloop join -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) ON (r+i)<100; -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100; -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) ON (r+i)<100; -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100; -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN ROWS FROM( foo_sql(11,13), foo_mat(11,13) ) WITH ORDINALITY AS f(i1,s1,i2,s2,o) ON (r+i1+i2)<100; SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) f(i) ON (r+i)<100; @@ -262,42 +262,42 @@ SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) WITH O --invokes ExecReScanFunctionScan with chgParam != NULL (using implied LATERAL) -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13); -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13) WITH ORDINALITY AS f(i,s,o); -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r); -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r) WITH ORDINALITY AS f(i,s,o); -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2); -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2) WITH ORDINALITY AS f(i,s,o); -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13); -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13) WITH ORDINALITY AS f(i,s,o); -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r); -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r) WITH ORDINALITY AS f(i,s,o); -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2); -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2) WITH ORDINALITY AS f(i,s,o); -- selective rescan of multiple functions: -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); SELECT * FROM (VALUES (1),(2),(3)) v(r), ROWS FROM( foo_sql(11,11), foo_mat(10+r,13) ); -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); SELECT * FROM (VALUES (1),(2),(3)) v(r), ROWS FROM( foo_sql(10+r,13), foo_mat(11,11) ); -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); SELECT * FROM (VALUES (1),(2),(3)) v(r), ROWS FROM( foo_sql(10+r,13), foo_mat(10+r,13) ); -SELECT setval('foo_rescan_seq1',1,false),setval('foo_rescan_seq2',1,false); +SELECT pg_sequence_restore_state('foo_rescan_seq1','1,false'),pg_sequence_restore_state('foo_rescan_seq2','1,false'); SELECT * FROM generate_series(1,2) r1, generate_series(r1,3) r2, ROWS FROM( foo_sql(10+r1,13), foo_mat(10+r2,13) ); SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) f(i); diff --git a/src/test/regress/sql/sequence.sql b/src/test/regress/sql/sequence.sql index 0dd653d..aa60bfd 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_restore_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_restore_state('sequence_test'::regclass, '99, false'); SELECT nextval('sequence_test'::text); DISCARD SEQUENCES; SELECT currval('sequence_test'::regclass);