From 64c69fe0eedfa5c4ab699a2664acdfe347f98c95 Mon Sep 17 00:00:00 2001
From: Oleg Babin <babinoleg@mail.ru>
Date: Sun, 2 Feb 2020 15:13:30 +0300
Subject: [PATCH] box: allow to retrieve the last generated value of sequence

This patch introduces "current" function for sequences.
It returns the last retrieved value of specified sequence or
throws an error if no value has been generated yet.

This patch partially reverts 3ff1f1e36e14381c0ebb5862943d4da281254767
(box: remove sequence_get) here similar function "get" was removed
to avoid possible misleading with "currval" function of PosgreSQL
that returns the last obtained value of the sequence in the scope
of current session. In contrast "current" returns the last globally
retrieved value of the sequence.

Closes #4752

Reviewed-by: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>
Reviewed-by: Nikita Pettik <korablev@tarantool.org>

@TarantoolBot document
Title: sequence:current()

This patch introduces "current" function for sequences.
It returns the last retrieved value of specified sequence or
throws an error if no value has been generated yet ("next"
has not been called yet or right after "reset" is called).

Lua:

Example:

```lua
sq = box.schema.sequence.create('test')
---
...
sq:current()
---
- error: Sequence 'test' is not started
...
sq:next()
---
- 1
...
sq:current()
---
- 1
...
sq:set(42)
---
...
sq:current()
---
- 42
...
sq:reset()
---
...
sq:current()  -- error
---
- error: Sequence 'test' is not started
...
```

C API:

```C
int
box_sequence_current(uint32_t seq_id, int64_t *result);
```

Where:
  * seq_id - sequence identifier;
  * result - pointer to a variable where the current sequence
  value will be stored on success.

Returns 0 on success and -1 otherwise. In case of an error user
could get it via `box_error_last()`.
---
 extra/exports              |  1 +
 src/box/box.cc             | 14 ++++++++
 src/box/box.h              | 12 +++++++
 src/box/errcode.h          |  1 +
 src/box/lua/schema.lua     | 17 +++++++--
 src/box/sequence.c         | 12 ++++---
 src/box/sequence.h         |  7 ++--
 src/box/sql/vdbe.c         |  4 ++-
 src/lua/buffer.lua         |  1 +
 test/box/misc.result       |  1 +
 test/box/sequence.result   | 73 ++++++++++++++++++++++++++++++++++++++
 test/box/sequence.test.lua | 24 +++++++++++++
 12 files changed, 157 insertions(+), 10 deletions(-)

diff --git a/extra/exports b/extra/exports
index 3a06373175..cbb5adcf45 100644
--- a/extra/exports
+++ b/extra/exports
@@ -215,6 +215,7 @@ box_update
 box_upsert
 box_truncate
 box_sequence_next
+box_sequence_current
 box_sequence_set
 box_sequence_reset
 box_index_iterator
diff --git a/src/box/box.cc b/src/box/box.cc
index 09dd67ab47..a5052dba4b 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -1414,6 +1414,20 @@ box_sequence_next(uint32_t seq_id, int64_t *result)
 	*result = value;
 	return 0;
 }
+
+int
+box_sequence_current(uint32_t seq_id, int64_t *result)
+{
+	struct sequence *seq = sequence_cache_find(seq_id);
+	if (seq == NULL)
+		return -1;
+	if (access_check_sequence(seq) != 0)
+		return -1;
+	if (sequence_get_value(seq, result) != 0)
+		return -1;
+	return 0;
+}
+
 int
 box_sequence_set(uint32_t seq_id, int64_t value)
 {
diff --git a/src/box/box.h b/src/box/box.h
index f37a945ebd..044d929d45 100644
--- a/src/box/box.h
+++ b/src/box/box.h
@@ -423,6 +423,18 @@ box_truncate(uint32_t space_id);
 API_EXPORT int
 box_sequence_next(uint32_t seq_id, int64_t *result);
 
+/**
+ * Get the last value returned by a sequence.
+ *
+ * \param seq_id sequence identifier
+ * \param[out] result pointer to a variable where the current sequence
+ * value will be stored on success
+ * \retval -1 on error (check box_error_last())
+ * \retval 0 on success
+ */
+API_EXPORT int
+box_sequence_current(uint32_t seq_id, int64_t *result);
+
 /**
  * Set a sequence value.
  *
diff --git a/src/box/errcode.h b/src/box/errcode.h
index d7ec97e8c2..444171778b 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -264,6 +264,7 @@ struct errcode_record {
 	/*209 */_(ER_SESSION_SETTING_INVALID_VALUE,	"Session setting %s expected a value of type %s") \
 	/*210 */_(ER_SQL_PREPARE,		"Failed to prepare SQL statement: %s") \
 	/*211 */_(ER_WRONG_QUERY_ID,		"Prepared statement with id %u does not exist") \
+	/*212 */_(ER_SEQUENCE_NOT_STARTED,		"Sequence '%s' is not started") \
 
 /*
  * !IMPORTANT! Please follow instructions at start of the file
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 774200191c..85fcca562a 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -7,7 +7,7 @@ local fun = require('fun')
 local log = require('log')
 local fio = require('fio')
 local json = require('json')
-local static_alloc = require('buffer').static_alloc
+local buffer = require('buffer')
 local session = box.session
 local internal = require('box.internal')
 local function setmap(table)
@@ -72,6 +72,10 @@ ffi.cdef[[
     int
     box_txn_begin();
     /** \endcond public */
+    /** \cond public */
+    int
+    box_sequence_current(uint32_t seq_id, int64_t *result);
+    /** \endcond public */
     typedef struct txn_savepoint box_txn_savepoint_t;
 
     box_txn_savepoint_t *
@@ -1812,6 +1816,15 @@ sequence_mt.next = function(self)
     return internal.sequence.next(self.id)
 end
 
+sequence_mt.current = function(self)
+    local ai64 = buffer.reg1.ai64
+    local rc = builtin.box_sequence_current(self.id, ai64)
+    if rc < 0 then
+        box.error(box.error.last())
+    end
+    return ai64[0]
+end
+
 sequence_mt.set = function(self, value)
     return internal.sequence.set(self.id, value)
 end
@@ -2295,7 +2308,7 @@ box.schema.user = {}
 
 box.schema.user.password = function(password)
     local BUF_SIZE = 128
-    local buf = static_alloc('char', BUF_SIZE)
+    local buf = buffer.static_alloc('char', BUF_SIZE)
     builtin.password_prepare(password, #password, buf, BUF_SIZE)
     return ffi.string(buf)
 end
diff --git a/src/box/sequence.c b/src/box/sequence.c
index 5ebfa2747a..4afbc26668 100644
--- a/src/box/sequence.c
+++ b/src/box/sequence.c
@@ -367,14 +367,18 @@ sequence_data_iterator_create(void)
 	return &iter->base;
 }
 
-int64_t
-sequence_get_value(struct sequence *seq)
+int
+sequence_get_value(struct sequence *seq, int64_t *result)
 {
 	uint32_t key = seq->def->id;
 	uint32_t hash = sequence_hash(key);
 	uint32_t pos = light_sequence_find_key(&sequence_data_index, hash, key);
-	assert(pos != light_sequence_end);
+	if (pos == light_sequence_end) {
+		diag_set(ClientError, ER_SEQUENCE_NOT_STARTED, seq->def->name);
+		return -1;
+	}
 	struct sequence_data data = light_sequence_get(&sequence_data_index,
 						       pos);
-	return data.value;
+	*result = data.value;
+	return 0;
 }
diff --git a/src/box/sequence.h b/src/box/sequence.h
index a164da9afb..400bdc6f99 100644
--- a/src/box/sequence.h
+++ b/src/box/sequence.h
@@ -171,10 +171,11 @@ sequence_data_iterator_create(void);
  * Get last element of given sequence.
  *
  * @param seq sequence to get value from.
- * @retval last element of sequence.
+ * On success, return 0 and assign the current value of the
+ * sequence to @result, otherwise return -1 and set diag.
  */
-int64_t
-sequence_get_value(struct sequence *seq);
+int
+sequence_get_value(struct sequence *seq, int64_t *result);
 
 #if defined(__cplusplus)
 } /* extern "C" */
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 912a101537..e8a029a8ae 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4328,7 +4328,9 @@ case OP_IdxInsert: {
 		p->nChange++;
 	if (pOp->p3 > 0 && ((aMem[pOp->p3].flags) & MEM_Null) != 0) {
 		assert(space->sequence != NULL);
-		int64_t value = sequence_get_value(space->sequence);
+		int64_t value;
+		if (sequence_get_value(space->sequence, &value) != 0)
+			goto abort_due_to_error;
 		if (vdbe_add_new_autoinc_id(p, value) != 0)
 			goto abort_due_to_error;
 	}
diff --git a/src/lua/buffer.lua b/src/lua/buffer.lua
index ef0118c7c4..9aac82b398 100644
--- a/src/lua/buffer.lua
+++ b/src/lua/buffer.lua
@@ -63,6 +63,7 @@ union c_register {
     uint32_t u32;
     uint64_t u64;
     int64_t i64;
+    int64_t ai64[1];
 };
 ]]
 
diff --git a/test/box/misc.result b/test/box/misc.result
index 5ac5e0f26a..047591b60d 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -636,6 +636,7 @@ t;
   209: box.error.SESSION_SETTING_INVALID_VALUE
   210: box.error.SQL_PREPARE
   211: box.error.WRONG_QUERY_ID
+  212: box.error.SEQUENCE_NOT_STARTED
 ...
 test_run:cmd("setopt delimiter ''");
 ---
diff --git a/test/box/sequence.result b/test/box/sequence.result
index 32a094d6f2..eb1d80d1b8 100644
--- a/test/box/sequence.result
+++ b/test/box/sequence.result
@@ -188,10 +188,18 @@ sq.step, sq.min, sq.max, sq.start, sq.cycle
 - 1
 - false
 ...
+sq:current()  -- error
+---
+- error: Sequence 'test' is not started
+...
 sq:next() -- 1
 ---
 - 1
 ...
+sq:current()  -- 1
+---
+- 1
+...
 sq:next() -- 2
 ---
 - 2
@@ -199,6 +207,10 @@ sq:next() -- 2
 sq:set(100)
 ---
 ...
+sq:current()  -- 100
+---
+- 100
+...
 sq:next() -- 101
 ---
 - 101
@@ -210,6 +222,10 @@ sq:next() -- 102
 sq:reset()
 ---
 ...
+sq:current()  -- error
+---
+- error: Sequence 'test' is not started
+...
 sq:next() -- 1
 ---
 - 1
@@ -233,10 +249,18 @@ sq.step, sq.min, sq.max, sq.start, sq.cycle
 - -1
 - false
 ...
+sq:current()  -- error
+---
+- error: Sequence 'test' is not started
+...
 sq:next() -- -1
 ---
 - -1
 ...
+sq:current()  -- -1
+---
+- -1
+...
 sq:next() -- -2
 ---
 - -2
@@ -244,6 +268,10 @@ sq:next() -- -2
 sq:set(-100)
 ---
 ...
+sq:current()  -- -100
+---
+- -100
+...
 sq:next() -- -101
 ---
 - -101
@@ -255,6 +283,10 @@ sq:next() -- -102
 sq:reset()
 ---
 ...
+sq:current()  -- error
+---
+- error: Sequence 'test' is not started
+...
 sq:next() -- -1
 ---
 - -1
@@ -2306,3 +2338,44 @@ box.space._space_sequence:update({s.id}, {{'=', 2, t[2]}})
 s:drop()
 ---
 ...
+--
+-- gh-4752: introduce sequence:current() method which
+-- fetches current sequence value but doesn't modify
+-- sequence itself.
+--
+sq = box.schema.sequence.create('test')
+---
+...
+sq:current()
+---
+- error: Sequence 'test' is not started
+...
+sq:next()
+---
+- 1
+...
+sq:current()
+---
+- 1
+...
+sq:set(42)
+---
+...
+sq:current()
+---
+- 42
+...
+sq:current()
+---
+- 42
+...
+sq:reset()
+---
+...
+sq:current()
+---
+- error: Sequence 'test' is not started
+...
+sq:drop()
+---
+...
diff --git a/test/box/sequence.test.lua b/test/box/sequence.test.lua
index d8a212fab0..a49565b8a1 100644
--- a/test/box/sequence.test.lua
+++ b/test/box/sequence.test.lua
@@ -58,12 +58,16 @@ box.sequence.test == nil
 -- Default ascending sequence.
 sq = box.schema.sequence.create('test')
 sq.step, sq.min, sq.max, sq.start, sq.cycle
+sq:current()  -- error
 sq:next() -- 1
+sq:current()  -- 1
 sq:next() -- 2
 sq:set(100)
+sq:current()  -- 100
 sq:next() -- 101
 sq:next() -- 102
 sq:reset()
+sq:current()  -- error
 sq:next() -- 1
 sq:next() -- 2
 sq:drop()
@@ -71,12 +75,16 @@ sq:drop()
 -- Default descending sequence.
 sq = box.schema.sequence.create('test', {step = -1})
 sq.step, sq.min, sq.max, sq.start, sq.cycle
+sq:current()  -- error
 sq:next() -- -1
+sq:current()  -- -1
 sq:next() -- -2
 sq:set(-100)
+sq:current()  -- -100
 sq:next() -- -101
 sq:next() -- -102
 sq:reset()
+sq:current()  -- error
 sq:next() -- -1
 sq:next() -- -2
 sq:drop()
@@ -788,3 +796,19 @@ pk = s:create_index('pk', {sequence = true})
 t = box.space._space_sequence:get({s.id})
 box.space._space_sequence:update({s.id}, {{'=', 2, t[2]}})
 s:drop()
+
+--
+-- gh-4752: introduce sequence:current() method which
+-- fetches current sequence value but doesn't modify
+-- sequence itself.
+--
+sq = box.schema.sequence.create('test')
+sq:current()
+sq:next()
+sq:current()
+sq:set(42)
+sq:current()
+sq:current()
+sq:reset()
+sq:current()
+sq:drop()
-- 
GitLab