Hi,

First time PostgreSQL contributor here :)

I wanted to be able to allocate a bunch of numbers from a sequence at once. Multiple people seem to be struggling with this (https://stackoverflow.com/questions/896274/select-multiple-ids-from-a-postgresql-sequence, https://www.depesz.com/2008/03/20/getting-multiple-values-from-sequences/).

I propose to add an extra argument to nextval() that specifies how many numbers you want to allocate (default 1).

The attached patch (based on master) passes `./configure --enable-cassert --enable-debug && make && make check`, including the newly added regression tests.

It does change the signature of nextval_internal(), not sure if that's considered backwards compatibility breaking (for extensions?).

-- Jille
From 403993dfea71068070185dd14fa3f5ff26d5f791 Mon Sep 17 00:00:00 2001
From: Jille Timmermans <ji...@quis.cx>
Date: Sun, 27 Feb 2022 10:20:22 +0100
Subject: [PATCH] Add an argument to nextval() to grab multiple consecutive
 sequence numbers

---
 doc/src/sgml/func.sgml                 |  8 +++--
 src/backend/commands/sequence.c        | 46 +++++++++++++++++++++-----
 src/backend/executor/execExprInterp.c  |  2 +-
 src/backend/optimizer/util/clauses.c   |  2 +-
 src/include/catalog/pg_proc.dat        |  3 ++
 src/include/commands/sequence.h        |  2 +-
 src/test/regress/expected/sequence.out | 41 +++++++++++++++++++++++
 src/test/regress/sql/sequence.sql      | 12 +++++++
 8 files changed, 102 insertions(+), 14 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index df3cd5987b..5923ecc38e 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17605,7 +17605,7 @@ $.* ? (@ like_regex "^\\d+$")
         <indexterm>
          <primary>nextval</primary>
         </indexterm>
-        <function>nextval</function> ( <type>regclass</type> )
+        <function>nextval</function> ( <type>regclass</type> <optional>, <type>bigint</type> </optional> )
         <returnvalue>bigint</returnvalue>
        </para>
        <para>
@@ -17618,7 +17618,11 @@ $.* ? (@ like_regex "^\\d+$")
         values beginning with 1.  Other behaviors can be obtained by using
         appropriate parameters in the <xref linkend="sql-createsequence"/>
         command.
-      </para>
+       </para>
+       <para>
+        To grab multiple values you can pass an integer to <function>nextval</function>.
+        It will allocate that many consecutive numbers from the sequence and return the last value.
+       </para>
        <para>
         This function requires <literal>USAGE</literal>
         or <literal>UPDATE</literal> privilege on the sequence.
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index ab592ce2f1..79e2a1e7c0 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -570,7 +570,7 @@ nextval(PG_FUNCTION_ARGS)
 	 */
 	relid = RangeVarGetRelid(sequence, NoLock, false);
 
-	PG_RETURN_INT64(nextval_internal(relid, true));
+	PG_RETURN_INT64(nextval_internal(relid, true, 1));
 }
 
 Datum
@@ -578,11 +578,20 @@ nextval_oid(PG_FUNCTION_ARGS)
 {
 	Oid			relid = PG_GETARG_OID(0);
 
-	PG_RETURN_INT64(nextval_internal(relid, true));
+	PG_RETURN_INT64(nextval_internal(relid, true, 1));
+}
+
+Datum
+nextval_oid_num(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int64		num = PG_GETARG_INT64(1);
+
+	PG_RETURN_INT64(nextval_internal(relid, true, num));
 }
 
 int64
-nextval_internal(Oid relid, bool check_permissions)
+nextval_internal(Oid relid, bool check_permissions, int64 request)
 {
 	SeqTable	elm;
 	Relation	seqrel;
@@ -605,6 +614,17 @@ nextval_internal(Oid relid, bool check_permissions)
 	bool		cycle;
 	bool		logit = false;
 
+	if (request < 1)
+	{
+		char		buf[100];
+
+		snprintf(buf, sizeof(buf), INT64_FORMAT, request);
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("nextval: can't request %s values from a sequence",
+						buf)));
+	}
+
 	/* open and lock sequence */
 	init_sequence(relid, &elm, &seqrel);
 
@@ -627,11 +647,10 @@ nextval_internal(Oid relid, bool check_permissions)
 	 */
 	PreventCommandIfParallelMode("nextval()");
 
-	if (elm->last != elm->cached)	/* some numbers were cached */
+	if (elm->increment != 0 && (elm->cached - elm->last) / elm->increment >= request)	/* enough numbers were cached */
 	{
 		Assert(elm->last_valid);
-		Assert(elm->increment != 0);
-		elm->last += elm->increment;
+		elm->last += elm->increment * request;
 		relation_close(seqrel, NoLock);
 		last_used_seq = elm;
 		return elm->last;
@@ -652,8 +671,17 @@ nextval_internal(Oid relid, bool check_permissions)
 	seq = read_seq_tuple(seqrel, &buf, &seqdatatuple);
 	page = BufferGetPage(buf);
 
+	if (elm->cached != elm->last && elm->cached == seq->last_value) {
+		/*
+		 * There are some numbers in the cache, and we can grab the numbers directly following those.
+		 * We can fetch fewer new numbers and claim the numbers from the cache.
+		 */
+		request -= elm->cached - elm->last;
+	}
+
 	elm->increment = incby;
 	last = next = result = seq->last_value;
+	cache += request-1;
 	fetch = cache;
 	log = seq->log_cnt;
 
@@ -703,7 +731,7 @@ nextval_internal(Oid relid, bool check_permissions)
 			if ((maxv >= 0 && next > maxv - incby) ||
 				(maxv < 0 && next + incby > maxv))
 			{
-				if (rescnt > 0)
+				if (rescnt >= request)
 					break;		/* stop fetching */
 				if (!cycle)
 				{
@@ -726,7 +754,7 @@ nextval_internal(Oid relid, bool check_permissions)
 			if ((minv < 0 && next < minv - incby) ||
 				(minv >= 0 && next + incby < minv))
 			{
-				if (rescnt > 0)
+				if (rescnt >= request)
 					break;		/* stop fetching */
 				if (!cycle)
 				{
@@ -749,7 +777,7 @@ nextval_internal(Oid relid, bool check_permissions)
 			log--;
 			rescnt++;
 			last = next;
-			if (rescnt == 1)	/* if it's first result - */
+			if (rescnt == request)	/* if this is the Nth result when we're requesting N numbers - */
 				result = next;	/* it's what to return */
 		}
 	}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index d6f7d7c2d7..f600a4ea9f 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2547,7 +2547,7 @@ ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op)
 void
 ExecEvalNextValueExpr(ExprState *state, ExprEvalStep *op)
 {
-	int64		newval = nextval_internal(op->d.nextvalueexpr.seqid, false);
+	int64		newval = nextval_internal(op->d.nextvalueexpr.seqid, false, 1);
 
 	switch (op->d.nextvalueexpr.seqtypid)
 	{
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index a707dc9f26..417000277a 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -565,7 +565,7 @@ contain_volatile_functions_not_nextval(Node *clause)
 static bool
 contain_volatile_functions_not_nextval_checker(Oid func_id, void *context)
 {
-	return (func_id != F_NEXTVAL &&
+	return (func_id != F_NEXTVAL_REGCLASS && func_id != F_NEXTVAL_REGCLASS_INT8 &&
 			func_volatile(func_id) == PROVOLATILE_VOLATILE);
 }
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7de8cfc7e9..85453627c3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3259,6 +3259,9 @@
 { oid => '1574', descr => 'sequence next value',
   proname => 'nextval', provolatile => 'v', proparallel => 'u',
   prorettype => 'int8', proargtypes => 'regclass', prosrc => 'nextval_oid' },
+{ oid => '1568', descr => 'sequence multiple next values',
+  proname => 'nextval', provolatile => 'v', proparallel => 'u',
+  prorettype => 'int8', proargtypes => 'regclass int8', prosrc => 'nextval_oid_num' },
 { oid => '1575', descr => 'sequence current value',
   proname => 'currval', provolatile => 'v', proparallel => 'u',
   prorettype => 'int8', proargtypes => 'regclass', prosrc => 'currval_oid' },
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 9fecc41954..4cad144574 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -52,7 +52,7 @@ typedef struct xl_seq_rec
 	/* SEQUENCE TUPLE DATA FOLLOWS AT THE END */
 } xl_seq_rec;
 
-extern int64 nextval_internal(Oid relid, bool check_permissions);
+extern int64 nextval_internal(Oid relid, bool check_permissions, int64 request);
 extern Datum nextval(PG_FUNCTION_ARGS);
 extern List *sequence_options(Oid relid);
 
diff --git a/src/test/regress/expected/sequence.out b/src/test/regress/expected/sequence.out
index 71c2b0f1df..e6b739dfca 100644
--- a/src/test/regress/expected/sequence.out
+++ b/src/test/regress/expected/sequence.out
@@ -822,4 +822,45 @@ SELECT nextval('test_seq1');
        3
 (1 row)
 
+SELECT nextval('test_seq1', 2);
+ nextval 
+---------
+       5
+(1 row)
+
+SELECT nextval('test_seq1', 30);
+ nextval 
+---------
+      35
+(1 row)
+
 DROP SEQUENCE test_seq1;
+-- grab multiple values
+CREATE SEQUENCE multi_seq;
+SELECT nextval('multi_seq', 2);
+ nextval 
+---------
+       2
+(1 row)
+
+SELECT nextval('multi_seq', -1); -- error
+ERROR:  nextval: can't request -1 values from a sequence
+SELECT nextval('multi_seq', 6);
+ nextval 
+---------
+       8
+(1 row)
+
+CREATE SEQUENCE multi_reverse_seq INCREMENT BY -1;
+SELECT nextval('multi_reverse_seq', 2);
+ nextval 
+---------
+      -2
+(1 row)
+
+SELECT nextval('multi_reverse_seq', 2);
+ nextval 
+---------
+      -4
+(1 row)
+
diff --git a/src/test/regress/sql/sequence.sql b/src/test/regress/sql/sequence.sql
index 7928ee23ee..591dc74d84 100644
--- a/src/test/regress/sql/sequence.sql
+++ b/src/test/regress/sql/sequence.sql
@@ -404,5 +404,17 @@ CREATE SEQUENCE test_seq1 CACHE 10;
 SELECT nextval('test_seq1');
 SELECT nextval('test_seq1');
 SELECT nextval('test_seq1');
+SELECT nextval('test_seq1', 2);
+SELECT nextval('test_seq1', 30);
 
 DROP SEQUENCE test_seq1;
+
+-- grab multiple values
+CREATE SEQUENCE multi_seq;
+SELECT nextval('multi_seq', 2);
+SELECT nextval('multi_seq', -1); -- error
+SELECT nextval('multi_seq', 6);
+
+CREATE SEQUENCE multi_reverse_seq INCREMENT BY -1;
+SELECT nextval('multi_reverse_seq', 2);
+SELECT nextval('multi_reverse_seq', 2);
-- 
2.32.0

Reply via email to