0003-gapless-sequence-v5.patch
application/x-patch
Filename: 0003-gapless-sequence-v5.patch
Type: application/x-patch
Part: 2
Message:
Re: Sequence Access Method WIP
Patch
Format: format-patch
Series: patch v5-0003
Subject: gapless sequence v5
| File | + | − |
|---|---|---|
| contrib/gapless_seq/expected/concurrency.out | 31 | 0 |
| contrib/gapless_seq/expected/gapless_seq.out | 145 | 0 |
| contrib/gapless_seq/gapless_seq--1.0.sql | 78 | 0 |
| contrib/gapless_seq/gapless_seq.c | 536 | 0 |
| contrib/gapless_seq/gapless_seq.control | 6 | 0 |
| contrib/gapless_seq/gapless_seq.sgml | 22 | 0 |
| contrib/gapless_seq/Makefile | 63 | 0 |
| contrib/gapless_seq/specs/concurrency.spec | 29 | 0 |
| contrib/gapless_seq/sql/gapless_seq.sql | 61 | 0 |
From afbc19973d6db1d5c6ad518c9d376d26b4c27517 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Tue, 9 Dec 2014 22:57:53 +0100
Subject: [PATCH 3/3] gapless sequence v5
---
contrib/gapless_seq/Makefile | 63 ++++
contrib/gapless_seq/expected/concurrency.out | 31 ++
contrib/gapless_seq/expected/gapless_seq.out | 145 ++++++++
contrib/gapless_seq/gapless_seq--1.0.sql | 78 ++++
contrib/gapless_seq/gapless_seq.c | 536 +++++++++++++++++++++++++++
contrib/gapless_seq/gapless_seq.control | 6 +
contrib/gapless_seq/gapless_seq.sgml | 22 ++
contrib/gapless_seq/specs/concurrency.spec | 29 ++
contrib/gapless_seq/sql/gapless_seq.sql | 61 +++
9 files changed, 971 insertions(+)
create mode 100644 contrib/gapless_seq/Makefile
create mode 100644 contrib/gapless_seq/expected/concurrency.out
create mode 100644 contrib/gapless_seq/expected/gapless_seq.out
create mode 100644 contrib/gapless_seq/gapless_seq--1.0.sql
create mode 100644 contrib/gapless_seq/gapless_seq.c
create mode 100644 contrib/gapless_seq/gapless_seq.control
create mode 100644 contrib/gapless_seq/gapless_seq.sgml
create mode 100644 contrib/gapless_seq/specs/concurrency.spec
create mode 100644 contrib/gapless_seq/sql/gapless_seq.sql
diff --git a/contrib/gapless_seq/Makefile b/contrib/gapless_seq/Makefile
new file mode 100644
index 0000000..9378b93
--- /dev/null
+++ b/contrib/gapless_seq/Makefile
@@ -0,0 +1,63 @@
+# contrib/gapless_seq/Makefile
+
+MODULE_big = gapless_seq
+OBJS = gapless_seq.o
+PG_CPPFLAGS = -I$(libpq_srcdir)
+
+EXTENSION = gapless_seq
+DATA = gapless_seq--1.0.sql
+
+EXTRA_CLEAN = $(pg_regress_clean_files) ./regression_output ./isolation_output
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/gapless_seq
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
+
+check: regresscheck isolationcheck
+
+submake-regress:
+ $(MAKE) -C $(top_builddir)/src/test/regress all
+
+submake-isolation:
+ $(MAKE) -C $(top_builddir)/src/test/isolation all
+
+submake-gapless_seq:
+ $(MAKE) -C $(top_builddir)/contrib/gapless_seq
+
+REGRESSCHECKS=gapless_seq
+
+regresscheck: all | submake-regress submake-gapless_seq temp-install
+ $(MKDIR_P) regression_output
+ $(pg_regress_check) \
+ --temp-instance=./tmp_check \
+ --outputdir=./regression_output \
+ $(REGRESSCHECKS)
+
+regresscheck-install-force: | submake-regress submake-gapless_seq temp-install
+ $(pg_regress_installcheck) \
+ $(REGRESSCHECKS)
+
+ISOLATIONCHECKS=concurrency
+
+isolationcheck: all | submake-isolation submake-gapless_seq temp-install
+ $(MKDIR_P) isolation_output
+ $(pg_isolation_regress_check) \
+ --outputdir=./isolation_output \
+ $(ISOLATIONCHECKS)
+
+isolationcheck-install-force: all | submake-isolation submake-gapless_seq temp-install
+ $(pg_isolation_regress_installcheck) \
+ $(ISOLATIONCHECKS)
+
+PHONY: submake-gapless_seq submake-regress check \
+ regresscheck regresscheck-install-force \
+ isolationcheck isolationcheck-install-force
+
+temp-install: EXTRA_INSTALL=contrib/gapless_seq
diff --git a/contrib/gapless_seq/expected/concurrency.out b/contrib/gapless_seq/expected/concurrency.out
new file mode 100644
index 0000000..ec6a098
--- /dev/null
+++ b/contrib/gapless_seq/expected/concurrency.out
@@ -0,0 +1,31 @@
+Parsed test spec with 3 sessions
+
+starting permutation: s1_begin s1_nextval s2_begin s2_nextval s1_commit s2_commit
+step s1_begin: BEGIN;
+step s1_nextval: SELECT nextval('test_gapless'::regclass);
+nextval
+
+1
+step s2_begin: BEGIN;
+step s2_nextval: SELECT nextval('test_gapless'::regclass); <waiting ...>
+step s1_commit: COMMIT;
+step s2_nextval: <... completed>
+nextval
+
+2
+step s2_commit: COMMIT;
+
+starting permutation: s3_begin s3_nextval s2_begin s2_nextval s3_rollback s2_commit
+step s3_begin: BEGIN;
+step s3_nextval: SELECT nextval('test_gapless'::regclass);
+nextval
+
+1
+step s2_begin: BEGIN;
+step s2_nextval: SELECT nextval('test_gapless'::regclass); <waiting ...>
+step s3_rollback: ROLLBACK;
+step s2_nextval: <... completed>
+nextval
+
+1
+step s2_commit: COMMIT;
diff --git a/contrib/gapless_seq/expected/gapless_seq.out b/contrib/gapless_seq/expected/gapless_seq.out
new file mode 100644
index 0000000..c2dd1be
--- /dev/null
+++ b/contrib/gapless_seq/expected/gapless_seq.out
@@ -0,0 +1,145 @@
+CREATE EXTENSION gapless_seq;
+CREATE SEQUENCE test_gapless USING gapless;
+SELECT nextval('test_gapless'::regclass);
+ nextval
+---------
+ 1
+(1 row)
+
+BEGIN;
+ SELECT nextval('test_gapless'::regclass);
+ nextval
+---------
+ 2
+(1 row)
+
+ SELECT nextval('test_gapless'::regclass);
+ nextval
+---------
+ 3
+(1 row)
+
+ SELECT nextval('test_gapless'::regclass);
+ nextval
+---------
+ 4
+(1 row)
+
+ROLLBACK;
+SELECT nextval('test_gapless'::regclass);
+ nextval
+---------
+ 2
+(1 row)
+
+CREATE SEQUENCE test_alter_seq USING local;
+SELECT nextval('test_alter_seq'::regclass);
+ nextval
+---------
+ 1
+(1 row)
+
+ALTER SEQUENCE test_alter_seq USING gapless;
+SELECT nextval('test_alter_seq'::regclass);
+ nextval
+---------
+ 2
+(1 row)
+
+BEGIN;
+ SELECT nextval('test_alter_seq'::regclass);
+ nextval
+---------
+ 3
+(1 row)
+
+ROLLBACK;
+SELECT nextval('test_alter_seq'::regclass);
+ nextval
+---------
+ 3
+(1 row)
+
+BEGIN;
+ SELECT nextval('test_alter_seq'::regclass);
+ nextval
+---------
+ 4
+(1 row)
+
+ SAVEPOINT mysp;
+ SELECT nextval('test_alter_seq'::regclass);
+ nextval
+---------
+ 5
+(1 row)
+
+ ROLLBACK TO SAVEPOINT mysp;
+ SAVEPOINT mysp2;
+ SELECT nextval('test_alter_seq'::regclass);
+ nextval
+---------
+ 5
+(1 row)
+
+ RELEASE SAVEPOINT mysp2;
+ SELECT nextval('test_alter_seq'::regclass);
+ nextval
+---------
+ 6
+(1 row)
+
+COMMIT;
+ALTER SEQUENCE test_alter_seq RESTART 100 USING local;
+SELECT nextval('test_alter_seq'::regclass);
+ nextval
+---------
+ 100
+(1 row)
+
+-- check dump/restore
+SELECT pg_sequence_get_state('test_gapless');
+ pg_sequence_get_state
+----------------------------
+ {last_value,2,is_called,t}
+(1 row)
+
+SELECT pg_sequence_set_state('test_gapless', pg_sequence_get_state('test_gapless'));
+ pg_sequence_set_state
+-----------------------
+
+(1 row)
+
+SELECT pg_sequence_get_state('test_gapless');
+ pg_sequence_get_state
+----------------------------
+ {last_value,2,is_called,t}
+(1 row)
+
+-- check that event trigger works correctly
+SELECT last_value FROM gapless_seq.seqam_gapless_values ORDER BY seqid;
+ last_value
+------------
+ 2
+ 6
+(2 rows)
+
+DROP SEQUENCE test_gapless;
+SELECT last_value FROM gapless_seq.seqam_gapless_values ORDER BY seqid;
+ last_value
+------------
+(0 rows)
+
+CREATE SEQUENCE test_gapless USING gapless;
+-- should fail due to deps
+DROP ACCESS METHOD FOR SEQUENCES gapless;
+ERROR: cannot drop sequence access method gapless because extension gapless_seq requires it
+HINT: You can drop extension gapless_seq instead.
+-- likewise
+DROP EXTENSION gapless_seq;
+ERROR: cannot drop extension gapless_seq because other objects depend on it
+DETAIL: sequence test_gapless depends on sequence access method gapless
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+-- success
+DROP EXTENSION gapless_seq CASCADE;
+NOTICE: drop cascades to sequence test_gapless
diff --git a/contrib/gapless_seq/gapless_seq--1.0.sql b/contrib/gapless_seq/gapless_seq--1.0.sql
new file mode 100644
index 0000000..61f9c22
--- /dev/null
+++ b/contrib/gapless_seq/gapless_seq--1.0.sql
@@ -0,0 +1,78 @@
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION gapless_seq" to load this file. \quit
+
+CREATE OR REPLACE FUNCTION seqam_gapless_reloptions(INTERNAL, BOOLEAN)
+RETURNS BYTEA
+LANGUAGE C
+STABLE STRICT
+AS 'MODULE_PATHNAME'
+;
+
+CREATE OR REPLACE FUNCTION seqam_gapless_init(INTERNAL, INTERNAL, BIGINT, BOOLEAN, BOOLEAN)
+RETURNS BYTEA
+LANGUAGE C
+STABLE STRICT
+AS 'MODULE_PATHNAME'
+;
+
+CREATE OR REPLACE FUNCTION seqam_gapless_alloc(INTERNAL, INTERNAL, BIGINT, INTERNAL)
+RETURNS BIGINT
+LANGUAGE C
+STABLE STRICT
+AS 'MODULE_PATHNAME'
+;
+
+CREATE OR REPLACE FUNCTION seqam_gapless_setval(INTERNAL, INTERNAL, BIGINT)
+RETURNS VOID
+LANGUAGE C
+STABLE STRICT
+AS 'MODULE_PATHNAME'
+;
+
+CREATE OR REPLACE FUNCTION seqam_gapless_get_state(INTERNAL, INTERNAL)
+RETURNS TEXT[]
+LANGUAGE C
+STABLE STRICT
+AS 'MODULE_PATHNAME'
+;
+
+CREATE OR REPLACE FUNCTION seqam_gapless_set_state(INTERNAL, INTERNAL, TEXT[])
+RETURNS VOID
+LANGUAGE C
+STABLE STRICT
+AS 'MODULE_PATHNAME'
+;
+
+CREATE TABLE seqam_gapless_values (
+ seqid oid PRIMARY KEY,
+ is_called bool NOT NULL,
+ last_value bigint NOT NULL
+);
+
+CREATE OR REPLACE FUNCTION gapless_seq_clean_sequence_value()
+RETURNS event_trigger
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ -- just delete all the value data that don't have corresponding
+ -- gapless sequence (either DELETEd or ALTERed to different AM)
+ DELETE FROM gapless_seq.seqam_gapless_values WHERE seqid NOT IN (
+ SELECT oid FROM pg_class WHERE relam = (
+ SELECT oid FROM pg_seqam WHERE seqamname = 'gapless'
+ )
+ );
+END;
+$$;
+
+CREATE EVENT TRIGGER gapless_seq_clean_sequence_value ON sql_drop
+ WHEN TAG IN ('DROP SEQUENCE')
+ EXECUTE PROCEDURE gapless_seq_clean_sequence_value();
+
+CREATE ACCESS METHOD FOR SEQUENCES gapless AS (
+ reloptions = seqam_gapless_reloptions, /* reloptions parser is same as local (no reloptions) */
+ init = seqam_gapless_init, /* init new gapless sequence */
+ alloc = seqam_gapless_alloc, /* logs and returns each value... slow */
+ setval = seqam_gapless_setval, /* setval support */
+ getstate = seqam_gapless_get_state, /* pgdump support */
+ setstate = seqam_gapless_set_state /* pgdump support */
+);
diff --git a/contrib/gapless_seq/gapless_seq.c b/contrib/gapless_seq/gapless_seq.c
new file mode 100644
index 0000000..c7c21d5
--- /dev/null
+++ b/contrib/gapless_seq/gapless_seq.c
@@ -0,0 +1,536 @@
+/*-------------------------------------------------------------------------
+ *
+ * gapless_seq.c
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * contrib/gapless_seq/gapless_seq.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/seqam.h"
+#include "access/transam.h"
+#include "access/xact.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_type.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/makefuncs.h"
+#include "storage/procarray.h"
+#include "storage/lmgr.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/int8.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+
+
+PG_MODULE_MAGIC;
+
+/*------------------------------------------------------------
+ *
+ * Sequence Access Manager = Gapless functions
+ *
+ *------------------------------------------------------------
+ */
+extern Datum seqam_gapless_reloptions(PG_FUNCTION_ARGS);
+extern Datum seqam_gapless_init(PG_FUNCTION_ARGS);
+extern Datum seqam_gapless_alloc(PG_FUNCTION_ARGS);
+extern Datum seqam_gapless_setval(PG_FUNCTION_ARGS);
+extern Datum seqam_gapless_get_state(PG_FUNCTION_ARGS);
+extern Datum seqam_gapless_set_state(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(seqam_gapless_reloptions);
+PG_FUNCTION_INFO_V1(seqam_gapless_init);
+PG_FUNCTION_INFO_V1(seqam_gapless_alloc);
+PG_FUNCTION_INFO_V1(seqam_gapless_setval);
+PG_FUNCTION_INFO_V1(seqam_gapless_get_state);
+PG_FUNCTION_INFO_V1(seqam_gapless_set_state);
+
+typedef struct GaplessAmdata
+{
+ uint32 xid;
+} GaplessAmdata;
+
+typedef struct GaplessValue
+{
+ Oid seqid;
+ bool is_called;
+ int64 last_value;
+} GaplessValue;
+
+#define GAPLESS_SEQ_NAMESPACE "gapless_seq"
+#define VALUES_TABLE_NAME "seqam_gapless_values"
+#define VALUES_TABLE_COLUMNS 3
+
+static FormData_pg_sequence *wait_for_sequence(SequenceHandle *seqh,
+ TransactionId local_xid);
+static Relation open_values_rel(void);
+static HeapTuple get_last_value_tup(Relation rel, Oid seqid);
+static void set_last_value_tup(Relation rel, Oid seqid, int64 last_value,
+ bool is_called, HeapTuple oldtuple);
+
+
+/*
+ * seqam_gapless_reloptions()
+ *
+ * Parse and verify the reloptions of a gapless sequence.
+ */
+Datum
+seqam_gapless_reloptions(PG_FUNCTION_ARGS)
+{
+ Datum reloptions = PG_GETARG_DATUM(0);
+ bool validate = PG_GETARG_BOOL(1);
+
+ if (validate && PointerIsValid(DatumGetPointer(reloptions)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("gapless sequence does not accept any storage parameters")));
+
+ PG_RETURN_NULL();
+}
+
+/*
+ * seqam_gapless_init()
+ *
+ * Initialize gapless sequence
+ *
+ */
+Datum
+seqam_gapless_init(PG_FUNCTION_ARGS)
+{
+ Relation seqrel = (Relation) PG_GETARG_POINTER(0);
+ HeapTuple tuple = (HeapTuple) PG_GETARG_POINTER(1);
+ int64 restart_value = (int64) PG_GETARG_INT64(2);
+ bool restart_requested = (bool) PG_GETARG_BOOL(3);
+ bool is_init = (bool) PG_GETARG_BOOL(4);
+ Oid seqrelid = seqrel->rd_id;
+ TupleDesc tupDesc = RelationGetDescr(seqrel);
+ Datum amdata;
+ bool isnull;
+ Relation valrel;
+ HeapTuple valtuple;
+ TransactionId local_xid = GetTopTransactionId();
+ GaplessAmdata *gapless_seq;
+
+ /* Load current value if this is existing sequence. */
+ valrel = open_values_rel();
+ valtuple = get_last_value_tup(valrel, seqrelid);
+
+ /*
+ * If this is new sequence or restart was provided or if there is
+ * no previous stored value for the sequence we should store it in
+ * the values table.
+ */
+ if (is_init || restart_requested || !HeapTupleIsValid(valtuple))
+ set_last_value_tup(valrel, seqrelid, restart_value, false, valtuple);
+
+ /* Now we are done with values relation, but keep the lock. */
+ heap_close(valrel, NoLock);
+
+ amdata = fastgetattr(tuple, SEQ_COL_AMDATA, tupDesc,
+ &isnull);
+
+ /* Make sure amdata is filled. */
+ if (isnull)
+ {
+ Datum values[SEQ_COL_LASTCOL];
+ bool nulls[SEQ_COL_LASTCOL];
+ bool replace[SEQ_COL_LASTCOL];
+ struct varlena *vl = palloc0(VARHDRSZ + sizeof(GaplessAmdata));
+ HeapTuple newtup;
+
+ memset(replace, false, sizeof(replace));
+ memset(nulls, false, sizeof(nulls));
+ memset(values, 0, sizeof(values));
+
+ replace[SEQ_COL_AMDATA - 1] = true;
+ nulls[SEQ_COL_AMDATA - 1] = false;
+
+ SET_VARSIZE(vl, VARHDRSZ + sizeof(GaplessAmdata));
+ values[SEQ_COL_AMDATA - 1] = PointerGetDatum(vl);
+
+ newtup = heap_modify_tuple(tuple, tupDesc, values, nulls, replace);
+
+ /* Don't leak memory. */
+ heap_freetuple(tuple);
+ tuple = newtup;
+
+ amdata = fastgetattr(tuple, SEQ_COL_AMDATA, tupDesc,
+ &isnull);
+ }
+
+ gapless_seq = (GaplessAmdata *)
+ VARDATA_ANY(DatumGetByteaP(amdata));
+
+ /* Update the xid info */
+ gapless_seq->xid = UInt32GetDatum(local_xid);
+
+ PG_RETURN_POINTER(tuple);
+}
+
+/*
+ * seqam_gapless_alloc()
+ *
+ * Allocate new value for gapless sequence.
+ */
+Datum
+seqam_gapless_alloc(PG_FUNCTION_ARGS)
+{
+ Relation seqrel = (Relation) PG_GETARG_POINTER(0);
+ SequenceHandle *seqh = (SequenceHandle*) PG_GETARG_POINTER(1);
+ /* we ignore nreguested as gapless sequence can't do caching */
+ int64 *last = (int64 *) PG_GETARG_POINTER(3);
+ int64 result;
+ Oid seqrelid = RelationGetRelid(seqrel);
+ Relation valrel;
+ HeapTuple tuple;
+ FormData_pg_sequence *seq;
+ GaplessAmdata *gapless_seq;
+ TransactionId local_xid = GetTopTransactionId();
+
+ /* Wait until the sequence is locked by us. */
+ seq = wait_for_sequence(seqh, local_xid);
+ gapless_seq = (GaplessAmdata *) VARDATA_ANY(&seq->amdata);
+
+ /* Read the last value from our transactional table (if any). */
+ valrel = open_values_rel();
+ tuple = get_last_value_tup(valrel, seqrelid);
+
+ /* Last value found get next value. */
+ if (HeapTupleIsValid(tuple))
+ {
+ GaplessValue *v = (GaplessValue *) GETSTRUCT(tuple);
+ result = v->last_value;
+
+ if (v->is_called)
+ (void) sequence_increment(seqrel, &result, 1, seq->min_value,
+ seq->max_value,
+ seq->increment_by,
+ seq->is_cycled, true);
+ }
+ else /* No last value, start from beginning. */
+ result = seq->start_value;
+
+ /*
+ * Insert or update the last value tuple.
+ */
+ set_last_value_tup(valrel, seqrelid, result, true, tuple);
+
+ /* Now we are done with values relation, but keep the lock. */
+ heap_close(valrel, NoLock);
+
+ /*
+ * If current tx is different fron the last one,
+ * update the sequence tuple as well.
+ *
+ * We don't need to WAL log the update as the only thing we save to
+ * sequence tuple is the active transaction id and we know that in case of
+ * crash the transaction id will not be active so it's ok to lose the
+ * update.
+ */
+ if (gapless_seq->xid != local_xid)
+ {
+ sequence_start_update(seqh, true);
+ gapless_seq->xid = local_xid;
+ sequence_apply_update(seqh, true);
+ sequence_finish_update(seqh);
+ }
+
+ *last = result;
+ PG_RETURN_INT64(result);
+}
+
+/*
+ * seqam_gapless_setval()
+ *
+ * Setval support (we don't allow setval on gapless)
+ */
+Datum
+seqam_gapless_setval(PG_FUNCTION_ARGS)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("setval() is not supported for gapless sequences")));
+
+ PG_RETURN_VOID();
+}
+
+/*
+ * seqam_gapless_get_state()
+ *
+ * Dump state of a gapless sequence (for pg_dump)
+ */
+Datum
+seqam_gapless_get_state(PG_FUNCTION_ARGS)
+{
+ Relation seqrel = (Relation) PG_GETARG_POINTER(0);
+ SequenceHandle *seqh = (SequenceHandle *) PG_GETARG_POINTER(1);
+ Datum datums[4];
+ Datum val;
+ Oid seqrelid = RelationGetRelid(seqrel);
+ Relation valrel;
+ HeapTuple tuple;
+ int64 last_value;
+ bool is_called;
+
+ /*
+ * Get the last value from the values table, if not found use start_value
+ * from the sequence definition.
+ */
+ valrel = open_values_rel();
+ tuple = get_last_value_tup(valrel, seqrelid);
+ heap_close(valrel, RowExclusiveLock);
+
+ if (HeapTupleIsValid(tuple))
+ {
+ GaplessValue *v = (GaplessValue *) GETSTRUCT(tuple);
+ last_value = v->last_value;
+ is_called = v->is_called;
+ }
+ else
+ {
+ FormData_pg_sequence *seq = (FormData_pg_sequence *)
+ GETSTRUCT(sequence_read_tuple(seqh));
+ last_value = seq->start_value;
+ is_called = false;
+ sequence_release_tuple(seqh);
+ }
+
+ datums[0] = CStringGetTextDatum("last_value");
+ val = DirectFunctionCall1(int8out, Int64GetDatum(last_value));
+ datums[1] = CStringGetTextDatum(DatumGetCString(val));
+
+ datums[2] = CStringGetTextDatum("is_called");
+ val = DirectFunctionCall1(boolout, BoolGetDatum(is_called));
+ datums[3] = CStringGetTextDatum(DatumGetCString(val));
+
+ PG_RETURN_ARRAYTYPE_P(construct_array(datums, 4, TEXTOID, -1, false, 'i'));
+}
+
+/*
+ * seqam_gapless_set_state()
+ *
+ * Restore previously dumpred state of gapless sequence
+ */
+Datum
+seqam_gapless_set_state(PG_FUNCTION_ARGS)
+{
+ Relation seqrel = (Relation) PG_GETARG_POINTER(0);
+ SequenceHandle *seqh = (SequenceHandle*) PG_GETARG_POINTER(1);
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P(2);
+ Oid seqrelid = RelationGetRelid(seqrel);
+ Relation valrel;
+ HeapTuple tuple;
+ int64 last_value = 0;
+ bool is_called = false;
+ bool last_value_found = false,
+ is_called_found = false;
+ Datum *datums;
+ int count;
+ int i;
+ FormData_pg_sequence *seq;
+ GaplessAmdata *gapless_seq;
+ TransactionId local_xid = GetTopTransactionId();
+
+ Assert(ARR_ELEMTYPE(array) == TEXTOID && ARR_NDIM(array) == 1 &&
+ (ARR_DIMS(array)[0]) % 2 == 0 && !ARR_HASNULL(array));
+
+ deconstruct_array(array,
+ TEXTOID, -1, false, 'i',
+ &datums, NULL, &count);
+
+ count /= 2;
+ for (i = 0; i < count; ++i)
+ {
+ char *key,
+ *val;
+
+ key = TextDatumGetCString(datums[i * 2]);
+ val = TextDatumGetCString(datums[i * 2 + 1]);
+
+ if (pg_strcasecmp(key, "last_value") == 0)
+ {
+ last_value = DatumGetInt64(DirectFunctionCall1(int8in,
+ CStringGetDatum(val)));
+ last_value_found = true;
+ }
+ else if (pg_strcasecmp(key, "is_called") == 0)
+ {
+ is_called = DatumGetBool(DirectFunctionCall1(boolin,
+ CStringGetDatum(val)));
+ is_called_found = true;
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid state key \"%s\" for gapless sequence",
+ key)));
+ }
+
+ if (!last_value_found)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("last_value is required parameter for gapless sequence")));
+
+ if (!is_called_found)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("is_called is required parameter for gapless sequence")));
+
+ /* Wait until the sequence is locked by us. */
+ seq = wait_for_sequence(seqh, local_xid);
+ gapless_seq = (GaplessAmdata *) VARDATA_ANY(&seq->amdata);
+
+ sequence_check_range(last_value, seq->min_value, seq->max_value, "last_value");
+
+ /* Read the last value from our transactional table (if any). */
+ valrel = open_values_rel();
+ tuple = get_last_value_tup(valrel, seqrelid);
+
+ /* Insert or update the last value tuple. */
+ set_last_value_tup(valrel, seqrelid, last_value, is_called, tuple);
+
+ /* Now we are done with values relation, but keep the lock. */
+ heap_close(valrel, NoLock);
+
+ /* Save to updated sequence. */
+ sequence_start_update(seqh, true);
+ gapless_seq->xid = local_xid;
+ sequence_apply_update(seqh, true);
+ sequence_finish_update(seqh);
+ sequence_release_tuple(seqh);
+
+ PG_FREE_IF_COPY(array, 2);
+
+ PG_RETURN_VOID();
+}
+
+/*
+ * Lock the sequence for current transaction.
+ */
+static FormData_pg_sequence *
+wait_for_sequence(SequenceHandle *seqh, TransactionId local_xid)
+{
+ FormData_pg_sequence *seq = (FormData_pg_sequence *) GETSTRUCT(sequence_read_tuple(seqh));
+ GaplessAmdata *gapless_seq = (GaplessAmdata *) VARDATA_ANY(&seq->amdata);
+
+ /*
+ * Read and lock the sequence for our transaction, there can't be any
+ * concurrent transactions accessing the sequence at the same time.
+ */
+ while (gapless_seq->xid != local_xid &&
+ TransactionIdIsInProgress(gapless_seq->xid))
+ {
+ /*
+ * Release tuple to avoid dead locks and wait for the concurrent tx
+ * to finish.
+ */
+ sequence_release_tuple(seqh);
+ XactLockTableWait(gapless_seq->xid, NULL, NULL, XLTW_None);
+ /* Reread the sequence. */
+ seq = (FormData_pg_sequence *) GETSTRUCT(sequence_read_tuple(seqh));
+ }
+
+ return seq;
+}
+
+/*
+ * Open the relation used for storing last value in RowExclusive lock mode.
+ */
+static Relation
+open_values_rel(void)
+{
+ RangeVar *rv;
+ Oid valrelid;
+ Relation valrel;
+
+ rv = makeRangeVar(GAPLESS_SEQ_NAMESPACE, VALUES_TABLE_NAME, -1);
+ valrelid = RangeVarGetRelid(rv, RowExclusiveLock, false);
+ valrel = heap_open(valrelid, RowExclusiveLock);
+
+ return valrel;
+}
+
+/*
+ * Read the last value tuple from the values table.
+ *
+ * Can return NULL if tuple is not found.
+ */
+static HeapTuple
+get_last_value_tup(Relation rel, Oid seqid)
+{
+ ScanKey key;
+ SysScanDesc scan;
+ HeapTuple tuple;
+
+ key = (ScanKey) palloc(sizeof(ScanKeyData) * 1);
+
+ ScanKeyInit(&key[0],
+ 1,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(seqid)
+ );
+
+ /* FIXME: should use index */
+ scan = systable_beginscan(rel, 0, true, NULL, 1, key);
+ tuple = systable_getnext(scan);
+ if (HeapTupleIsValid(tuple))
+ tuple = heap_copytuple(tuple);
+ systable_endscan(scan);
+
+ return tuple;
+}
+
+/*
+ * Insert or update the last value tuple.
+ *
+ * The write access to the table must be serialized by the wait_for_sequence
+ * so that we don't have to have any retry scheme here.
+*/
+static void
+set_last_value_tup(Relation rel, Oid seqid, int64 last_value, bool is_called, HeapTuple oldtuple)
+{
+ bool nulls[VALUES_TABLE_COLUMNS];
+ Datum values[VALUES_TABLE_COLUMNS];
+ TupleDesc tupDesc = RelationGetDescr(rel);
+ HeapTuple tuple;
+
+ if (!HeapTupleIsValid(oldtuple))
+ {
+ memset(nulls, false, VALUES_TABLE_COLUMNS * sizeof(bool));
+ values[0] = ObjectIdGetDatum(seqid);
+ values[1] = BoolGetDatum(is_called);
+ values[2] = Int64GetDatum(last_value);
+
+ tuple = heap_form_tuple(tupDesc, values, nulls);
+ simple_heap_insert(rel, tuple);
+ }
+ else
+ {
+ bool replaces[VALUES_TABLE_COLUMNS];
+
+ replaces[0] = false;
+ replaces[1] = true;
+ replaces[2] = true;
+
+ nulls[1] = false;
+ nulls[2] = false;
+ values[1] = BoolGetDatum(is_called);
+ values[2] = Int64GetDatum(last_value);
+
+ tuple = heap_modify_tuple(oldtuple, tupDesc, values, nulls, replaces);
+ simple_heap_update(rel, &tuple->t_self, tuple);
+ }
+
+ CatalogUpdateIndexes(rel, tuple);
+}
diff --git a/contrib/gapless_seq/gapless_seq.control b/contrib/gapless_seq/gapless_seq.control
new file mode 100644
index 0000000..85da739
--- /dev/null
+++ b/contrib/gapless_seq/gapless_seq.control
@@ -0,0 +1,6 @@
+# Gapless sequence extension
+comment = 'Gapless Sequence AM'
+default_version = '1.0'
+module_pathname = '$libdir/gapless_seq'
+relocatable = false
+schema = gapless_seq
diff --git a/contrib/gapless_seq/gapless_seq.sgml b/contrib/gapless_seq/gapless_seq.sgml
new file mode 100644
index 0000000..3eb536f
--- /dev/null
+++ b/contrib/gapless_seq/gapless_seq.sgml
@@ -0,0 +1,22 @@
+<!-- doc/src/sgml/gapless_seq.sgml -->
+<sect1 id="gapless_seq" xreflabel="gapless_seq">
+ <title>gapless sequence</title>
+
+ <indexterm zone="gapless_seq">
+ <primary>gapless_seq</primary>
+ </indexterm>
+
+ <para>
+ <filename>gapless_seq</> provides a sequence implementation that never
+ has gaps in the sequence of values it produces, even after rollback or
+ a crash.
+ </para>
+
+ <para>
+ The consequence of this capability is that every nextval() request
+ writes a WAL record recording the latest state of the sequence.
+ This could be very costly and is not recommended for general
+ usage except in specific applications that require this feature.
+ </para>
+
+</sect1>
diff --git a/contrib/gapless_seq/specs/concurrency.spec b/contrib/gapless_seq/specs/concurrency.spec
new file mode 100644
index 0000000..3a0cc57
--- /dev/null
+++ b/contrib/gapless_seq/specs/concurrency.spec
@@ -0,0 +1,29 @@
+setup
+{
+ CREATE EXTENSION IF NOT EXISTS gapless_seq;
+ DROP SEQUENCE IF EXISTS test_gapless;
+ CREATE SEQUENCE test_gapless USING gapless;
+}
+
+teardown
+{
+ DROP SEQUENCE test_gapless;
+}
+
+session "s1"
+step "s1_begin" { BEGIN; }
+step "s1_nextval" { SELECT nextval('test_gapless'::regclass); }
+step "s1_commit" { COMMIT; }
+
+session "s2"
+step "s2_begin" { BEGIN; }
+step "s2_nextval" { SELECT nextval('test_gapless'::regclass); }
+step "s2_commit" { COMMIT; }
+
+session "s3"
+step "s3_begin" { BEGIN; }
+step "s3_nextval" { SELECT nextval('test_gapless'::regclass); }
+step "s3_rollback" { ROLLBACK; }
+
+permutation "s1_begin" "s1_nextval" "s2_begin" "s2_nextval" "s1_commit" "s2_commit"
+permutation "s3_begin" "s3_nextval" "s2_begin" "s2_nextval" "s3_rollback" "s2_commit"
diff --git a/contrib/gapless_seq/sql/gapless_seq.sql b/contrib/gapless_seq/sql/gapless_seq.sql
new file mode 100644
index 0000000..1913947
--- /dev/null
+++ b/contrib/gapless_seq/sql/gapless_seq.sql
@@ -0,0 +1,61 @@
+CREATE EXTENSION gapless_seq;
+
+CREATE SEQUENCE test_gapless USING gapless;
+
+SELECT nextval('test_gapless'::regclass);
+
+BEGIN;
+ SELECT nextval('test_gapless'::regclass);
+ SELECT nextval('test_gapless'::regclass);
+ SELECT nextval('test_gapless'::regclass);
+ROLLBACK;
+
+SELECT nextval('test_gapless'::regclass);
+
+CREATE SEQUENCE test_alter_seq USING local;
+
+SELECT nextval('test_alter_seq'::regclass);
+
+ALTER SEQUENCE test_alter_seq USING gapless;
+
+SELECT nextval('test_alter_seq'::regclass);
+
+BEGIN;
+ SELECT nextval('test_alter_seq'::regclass);
+ROLLBACK;
+
+SELECT nextval('test_alter_seq'::regclass);
+
+BEGIN;
+ SELECT nextval('test_alter_seq'::regclass);
+ SAVEPOINT mysp;
+ SELECT nextval('test_alter_seq'::regclass);
+ ROLLBACK TO SAVEPOINT mysp;
+ SAVEPOINT mysp2;
+ SELECT nextval('test_alter_seq'::regclass);
+ RELEASE SAVEPOINT mysp2;
+ SELECT nextval('test_alter_seq'::regclass);
+COMMIT;
+
+ALTER SEQUENCE test_alter_seq RESTART 100 USING local;
+
+SELECT nextval('test_alter_seq'::regclass);
+
+-- check dump/restore
+SELECT pg_sequence_get_state('test_gapless');
+SELECT pg_sequence_set_state('test_gapless', pg_sequence_get_state('test_gapless'));
+SELECT pg_sequence_get_state('test_gapless');
+
+-- check that event trigger works correctly
+SELECT last_value FROM gapless_seq.seqam_gapless_values ORDER BY seqid;
+DROP SEQUENCE test_gapless;
+SELECT last_value FROM gapless_seq.seqam_gapless_values ORDER BY seqid;
+
+CREATE SEQUENCE test_gapless USING gapless;
+
+-- should fail due to deps
+DROP ACCESS METHOD FOR SEQUENCES gapless;
+-- likewise
+DROP EXTENSION gapless_seq;
+-- success
+DROP EXTENSION gapless_seq CASCADE;
--
1.9.1