[tarantool-patches] Re: [PATCH v2] sql: fix ephemeral table next rowid generation

Alex Khatskevich avkhatskevich at tarantool.org
Wed Aug 1 20:23:54 MSK 2018


>> diff --git a/src/box/memtx_space.c b/src/box/memtx_space.c
>> index f2d512f21..5a80d48ab 100644
>> --- a/src/box/memtx_space.c
>> +++ b/src/box/memtx_space.c
>> @@ -843,6 +843,8 @@ static void
>> memtx_init_ephemeral_space(struct space *space)
>> {
>> 	memtx_space_add_primary_key(space);
>> +	struct memtx_space * ephem_space = (struct memtx_space *) space;
>> +	ephem_space->next_rowid = 0;
>> }
>>
>> static int
>> diff --git a/src/box/memtx_space.h b/src/box/memtx_space.h
>> index 7dc341079..26181166a 100644
>> --- a/src/box/memtx_space.h
>> +++ b/src/box/memtx_space.h
>> @@ -48,6 +48,11 @@ struct memtx_space {
>> 	 */
>> 	int (*replace)(struct space *, struct tuple *, struct tuple *,
>> 		       enum dup_replace_mode, struct tuple **);
>> +	/**
>> +	 * Next rowid. This number is added to the end of pk to
> In fact, to the end of tuple to be inserted. Now ofc PK of ephemeral
> spaces consists of all fields, but it may change someday, I guess.
>
> Moreover, ephemeral spaces aren’t supposed to be feature exclusively
> of memtx engine. Vinyl ephemeral spaces also have right to exist.
1. Deleted those details.
2. Created vtab entity `ephemeral_next_rowid`.
>> +	 * allow to store non-unique rows in ephemeral tables.
>> +	 */
>> +	uint64_t next_rowid;
>> };
>>
>> /**
>> diff --git a/src/box/sql.c b/src/box/sql.c
>> index 6104a6d0f..fdf0313d3 100644
>> --- a/src/box/sql.c
>> +++ b/src/box/sql.c
>> @@ -47,6 +47,7 @@
>> #include "box.h"
>> #include "txn.h"
>> #include "space.h"
>> +#include "memtx_space.h"
>> #include "space_def.h"
>> #include "index_def.h"
>> #include "tuple.h"
>> @@ -448,6 +449,18 @@ int tarantoolSqlite3EphemeralDrop(BtCursor *pCur)
>> 	return SQLITE_OK;
>> }
>>
>> +/**
>> + * Get next rowid for an ephemeral table.
>> + */
>> +uint64_t
>> +tarantool_ephemeral_next_rowid(BtCursor *bt_cur)
> Firstly, we don’t use ’tarantool_’ prefix anymore. Also we use ’struct’ prefix
> for compound types: struct BtCursor *bt_cur. Moreover, I don’t see comment
> for this function.
fixed.
Added comment to `tarantoolInt.h`.
> Secondly, I see your message within issue discussion:
> "
> Discussed verbally with
> @kyukhin , @Korablev77 , @kostja and decided, that the unique rowid should be encapsulated into
> vtab method of ephemeral space.
>>
> But this function doesn’t seem to be method of vtab…
Created a dedicated vtab function.
>> +{
>> +	assert(bt_cur);
>> +	assert(bt_cur->curFlags & BTCF_TEphemCursor);
>> +	struct memtx_space * ephem_space = (struct memtx_space *) bt_cur->space;
>> +	return ephem_space->next_rowid++;
>> +}
>> +
>> static inline int
>> insertOrReplace(struct space *space, const char *tuple, const char *tuple_end,
>> 		enum iproto_type type)
>> @@ -1080,7 +1093,6 @@ key_alloc(BtCursor *cur, size_t key_size)
>> 		}
>> 		cur->key = new_key;
>> 	}
>> -	cur->nKey = key_size;
>> 	return 0;
>> }
>>
>> @@ -1621,37 +1633,6 @@ sql_debug_info(struct info_handler *h)
>> 	info_end(h);
>> }
>>
>>
>> diff --git a/src/box/sql/tarantoolInt.h b/src/box/sql/tarantoolInt.h
>> index 94f94cb44..369af77ff 100644
>> --- a/src/box/sql/tarantoolInt.h
>> +++ b/src/box/sql/tarantoolInt.h
>> @@ -116,8 +116,8 @@ int tarantoolSqlite3EphemeralDelete(BtCursor * pCur);
>> int tarantoolSqlite3EphemeralCount(BtCursor * pCur, i64 * pnEntry);
>> int tarantoolSqlite3EphemeralDrop(BtCursor * pCur);
>> int tarantoolSqlite3EphemeralClearTable(BtCursor * pCur);
>> -int tarantoolSqlite3EphemeralGetMaxId(BtCursor * pCur, uint32_t fieldno,
>> -				       uint64_t * max_id);
>> +uint64_t
>> +tarantool_ephemeral_next_rowid(BtCursor *bt_cur);
>>
>> /**
>>   * Performs exactly as extract_key + sqlite3VdbeCompareMsgpack,
>> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
>> index 89c35d218..532c98e54 100644
>> --- a/src/box/sql/vdbe.c
>> +++ b/src/box/sql/vdbe.c
>> @@ -3790,27 +3790,19 @@ case OP_NextSequenceId: {
>> 	break;
>> }
>>
>> -/* Opcode: NextIdEphemeral P1 P2 P3 * *
>> - * Synopsis: r[P3]=get_max(space_index[P1]{Column[P2]})
>> +/* Opcode: NextIdEphemeral P1 P2 * * *
>> + * Synopsis: r[P2]=get_next_rowid(space_index[P1]})
>>   *
>> - * This opcode works in the same way as OP_NextId does, except it is
>> - * only applied for ephemeral tables. The difference is in the fact that
>> - * all ephemeral tables don't have space_id (to be more precise it equals to zero).
>> + * This opcode stores next `rowid` for the ephemeral space to P2 register.
>> + * `rowid` is necessary, because inserted to ephemeral space tuples may be not
>> + * unique, while Tarantool`s spaces can contain only unique tuples.
> Nitpicking: generally speaking, ephemeral spaces can contain duplicates
> when it comes for NULLs. You may investigate how this request works:
>
> SELECT (SELECT NULL UNION SELECT NULL) EXCEPT SELECT NULL;
>
> :)
>
>>   */
>> case OP_NextIdEphemeral: {
>> 	VdbeCursor *pC;
>> -	int p2;
>> 	pC = p->apCsr[pOp->p1];
>> -	p2 = pOp->p2;
>> -	pOut = &aMem[pOp->p3];
>> -
>> -	assert(pC->uc.pCursor->curFlags & BTCF_TEphemCursor);
>> -
>> -	rc = tarantoolSqlite3EphemeralGetMaxId(pC->uc.pCursor, p2,
>> -					       (uint64_t *) &pOut->u.i);
>> -	if (rc) goto abort_due_to_error;
>> -
>> -	pOut->u.i += 1;
>> +	pOut = &aMem[pOp->p2];
>> +	struct BtCursor * bt_cur = pC->uc.pCursor;
>> +	pOut->u.i = tarantool_ephemeral_next_rowid(bt_cur);
> Should we add some checks on rowid overflow? Ephemeral spaces can be extensively
> used for JOIN’s where number of inserted to ephemeral space tuples is O(n^2) in
> worst case (for two tables). On the other hand, even simple check can slow down execution
> especially when it concerns millions of tuples...
In rowid is increasing 10^9 times per second, it would take 1017 years 
to overflow the counter.
So, no.
>> 	pOut->flags = MEM_Int;
>> 	break;
>> }
>> diff --git a/test/sql-tap/gh-3297_ephemeral_rowid.test.lua b/test/sql-tap/gh-3297_ephemeral_rowid.test.lua
>> new file mode 100755
>> index 000000000..ed596e7ad
>> --- /dev/null
>> +++ b/test/sql-tap/gh-3297_ephemeral_rowid.test.lua
>> @@ -0,0 +1,30 @@
>> +#!/usr/bin/env tarantool
>> +test = require("sqltester")
>> +test:plan(1)
>> +
>> +-- Check that OP_NextIdEphemeral generates unique ids.
>> +
>> +test:execsql [[
>> +    CREATE TABLE t1(a primary key);
>> +    CREATE TABLE t2(a primary key, b);
>> +    insert into t1 values(12);
>> +    insert into t2 values(1, 5);
>> +    insert into t2 values(2, 2);
>> +    insert into t2 values(3, 2);
>> +    insert into t2 values(4, 2);
>> +]]
>> +
>> +test:do_execsql_test(
>> +    "gh-3297-1",
>> +    [[
>> +        select * from ( select a from t1 limit 1), (select b from t2 limit 10);
> Use upper-case for all SQL statements pls.
Fixed.


Full diff

commit 3b30cd4ee27ccf9046a4623d6455479bf980cd1a
Author: AKhatskevich <avkhatskevich at tarantool.org>
Date:   Tue May 15 22:00:28 2018 +0300

     sql: fix ephemeral table next rowid generation

     Before this commit, next rowid was retrieved from an ephemeral table
     using `index_max`. That led to wrong behavior because the index is not
     sorted by rowid and `index_max` doesn`t return a tuple with the 
greatest
     rowid.

     New implementation stores next `rowid` value in `struct memtx_space`,
     and increments it after each insert to the ephemeral space.

     Extra refactoring:
      * `nKey` attribute is deleted from BtCursor, because it is not used
     anymore.

     Closes #3297

diff --git a/src/box/memtx_space.c b/src/box/memtx_space.c
index f2d512f21..9015c56ca 100644
--- a/src/box/memtx_space.c
+++ b/src/box/memtx_space.c
@@ -603,6 +603,20 @@ memtx_space_ephemeral_delete(struct space *space, 
const char *key)
      return 0;
  }

+/**
+ * Generate unique number.
+ * This rowid may be used to store non-unique values in an
+ * ephemeral space.
+ *
+ * This function isn't involved in any transaction routine.
+ */
+static uint64_t
+memtx_space_ephemeral_next_rowid(struct space *space)
+{
+    struct memtx_space *memtx_space = (struct memtx_space *)space;
+    return memtx_space->next_rowid++;
+}
+
  /* }}} DML */

  /* {{{ DDL */
@@ -843,6 +857,8 @@ static void
  memtx_init_ephemeral_space(struct space *space)
  {
      memtx_space_add_primary_key(space);
+    struct memtx_space * ephem_space = (struct memtx_space *) space;
+    ephem_space->next_rowid = 0;
  }

  static int
@@ -949,6 +965,7 @@ static const struct space_vtab memtx_space_vtab = {
      /* .execute_upsert = */ memtx_space_execute_upsert,
      /* .ephemeral_replace = */ memtx_space_ephemeral_replace,
      /* .ephemeral_delete = */ memtx_space_ephemeral_delete,
+    /* .ephemeral_next_rowid = */ memtx_space_ephemeral_next_rowid,
      /* .ephemeral_cleanup = */ memtx_space_ephemeral_cleanup,
      /* .init_system_space = */ memtx_init_system_space,
      /* .init_ephemeral_space = */ memtx_init_ephemeral_space,
diff --git a/src/box/memtx_space.h b/src/box/memtx_space.h
index 7dc341079..bb1d8b74b 100644
--- a/src/box/memtx_space.h
+++ b/src/box/memtx_space.h
@@ -48,6 +48,11 @@ struct memtx_space {
       */
      int (*replace)(struct space *, struct tuple *, struct tuple *,
                 enum dup_replace_mode, struct tuple **);
+    /**
+     * Next rowid. This number is used to allow to store
+     * non-unique rows in ephemeral tables.
+     */
+    uint64_t next_rowid;
  };

  /**
diff --git a/src/box/space.h b/src/box/space.h
index 8f675dfe1..e4ae29747 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -70,6 +70,10 @@ struct space_vtab {
      int (*ephemeral_replace)(struct space *, const char *, const char *);

      int (*ephemeral_delete)(struct space *, const char *);
+    /**
+     * Generate unique number.
+     */
+    uint64_t (*ephemeral_next_rowid)(struct space *);

      void (*ephemeral_cleanup)(struct space *);

diff --git a/src/box/sql.c b/src/box/sql.c
index 6104a6d0f..c27595afd 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -47,6 +47,7 @@
  #include "box.h"
  #include "txn.h"
  #include "space.h"
+#include "memtx_space.h"
  #include "space_def.h"
  #include "index_def.h"
  #include "tuple.h"
@@ -448,6 +449,15 @@ int tarantoolSqlite3EphemeralDrop(BtCursor *pCur)
      return SQLITE_OK;
  }

+uint64_t
+ephemeral_next_rowid(struct BtCursor *bt_cur)
+{
+    assert(bt_cur);
+    assert(bt_cur->curFlags & BTCF_TEphemCursor);
+    struct space * ephem_space = (struct space *) bt_cur->space;
+    return ephem_space->vtab->ephemeral_next_rowid(ephem_space);
+}
+
  static inline int
  insertOrReplace(struct space *space, const char *tuple, const char 
*tuple_end,
          enum iproto_type type)
@@ -1080,7 +1090,6 @@ key_alloc(BtCursor *cur, size_t key_size)
          }
          cur->key = new_key;
      }
-    cur->nKey = key_size;
      return 0;
  }

@@ -1621,37 +1630,6 @@ sql_debug_info(struct info_handler *h)
      info_end(h);
  }

-/*
- * Extract maximum integer value from ephemeral space.
- * If index is empty - return 0 in max_id and success status.
- *
- * @param pCur Cursor pointing to ephemeral space.
- * @param fieldno Number of field from fetching tuple.
- * @param[out] max_id Fetched max value.
- *
- * @retval 0 on success, -1 otherwise.
- */
-int tarantoolSqlite3EphemeralGetMaxId(BtCursor *pCur, uint32_t fieldno,
-                       uint64_t *max_id)
-{
-    struct space *ephem_space = pCur->space;
-    assert(ephem_space);
-    struct index *primary_index = *ephem_space->index;
-
-    struct tuple *tuple;
-    if (index_max(primary_index, NULL, 0, &tuple) != 0) {
-        return SQL_TARANTOOL_ERROR;
-    }
-    if (tuple == NULL) {
-        *max_id = 0;
-        return SQLITE_OK;
-    }
-    if (tuple_field_u64(tuple, fieldno, max_id) == -1)
-        return SQL_TARANTOOL_ERROR;
-
-    return SQLITE_OK;
-}
-
  int
  tarantoolSqlNextSeqId(uint64_t *max_id)
  {
diff --git a/src/box/sql/cursor.h b/src/box/sql/cursor.h
index c55941784..ab379f01e 100644
--- a/src/box/sql/cursor.h
+++ b/src/box/sql/cursor.h
@@ -57,7 +57,6 @@ typedef struct BtCursor BtCursor;
   * for ordinary space, or to TEphemCursor for ephemeral space.
   */
  struct BtCursor {
-    i64 nKey;        /* Size of pKey, or last integer key */
      u8 curFlags;        /* zero or more BTCF_* flags defined below */
      u8 eState;        /* One of the CURSOR_XXX constants (see below) */
      u8 hints;        /* As configured by CursorSetHints() */
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 0b9e75a93..bddfdc50c 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -557,15 +557,12 @@ sqlite3Insert(Parse * pParse,    /* Parser context */
               *      M: ...
               */
              int regRec;    /* Register to hold packed record */
-            int regTempId;    /* Register to hold temp table ID */
              int regCopy;    /* Register to keep copy of registers from 
select */
              int addrL;    /* Label "L" */
-            int64_t initial_pk = 0;

              srcTab = pParse->nTab++;
              regRec = sqlite3GetTempReg(pParse);
-            regCopy = sqlite3GetTempRange(pParse, nColumn);
-            regTempId = sqlite3GetTempReg(pParse);
+            regCopy = sqlite3GetTempRange(pParse, nColumn + 1);
              struct key_def *def = key_def_new(nColumn + 1);
              if (def == NULL) {
                  sqlite3OomFault(db);
@@ -573,16 +570,10 @@ sqlite3Insert(Parse * pParse,    /* Parser context */
              }
              sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, srcTab, nColumn+1,
                        0, (char*)def, P4_KEYDEF);
-            /* Create counter for rowid */
-            sqlite3VdbeAddOp4Dup8(v, OP_Int64,
-                          0 /* unused */,
-                          regTempId,
-                          0 /* unused */,
-                          (const unsigned char*) &initial_pk,
-                          P4_INT64);
              addrL = sqlite3VdbeAddOp1(v, OP_Yield, dest.iSDParm);
              VdbeCoverage(v);
-            sqlite3VdbeAddOp2(v, OP_AddImm, regTempId, 1);
+            sqlite3VdbeAddOp2(v, OP_NextIdEphemeral, srcTab,
+                      regCopy + nColumn);
              sqlite3VdbeAddOp3(v, OP_Copy, regFromSelect, regCopy, 
nColumn-1);
              sqlite3VdbeAddOp3(v, OP_MakeRecord, regCopy,
                        nColumn + 1, regRec);
@@ -593,7 +584,6 @@ sqlite3Insert(Parse * pParse,    /* Parser context */
              sqlite3VdbeGoto(v, addrL);
              sqlite3VdbeJumpHere(v, addrL);
              sqlite3ReleaseTempReg(pParse, regRec);
-            sqlite3ReleaseTempReg(pParse, regTempId);
              sqlite3ReleaseTempRange(pParse, regCopy, nColumn);
          }
      } else {
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 1f27efdb5..5cd289ccc 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -1047,8 +1047,8 @@ selectInnerLoop(Parse * pParse,     /* The parser 
context */
                  int regRec = sqlite3GetTempReg(pParse);
                  /* Last column is required for ID. */
                  int regCopy = sqlite3GetTempRange(pParse, nResultCol + 1);
-                sqlite3VdbeAddOp3(v, OP_NextIdEphemeral, iParm,
-                          nResultCol, regCopy + nResultCol);
+                sqlite3VdbeAddOp2(v, OP_NextIdEphemeral, iParm,
+                          regCopy + nResultCol);
                  /* Positioning ID column to be last in inserted tuple.
                   * NextId -> regCopy + n + 1
                   * Copy [regResult, regResult + n] -> [regCopy, 
regCopy + n]
@@ -1418,7 +1418,7 @@ generateSortTail(Parse * pParse, /* Parsing context */
      case SRT_Table:
      case SRT_EphemTab: {
              int regCopy = sqlite3GetTempRange(pParse, nColumn);
-            sqlite3VdbeAddOp3(v, OP_NextIdEphemeral, iParm, nColumn, 
regTupleid);
+            sqlite3VdbeAddOp2(v, OP_NextIdEphemeral, iParm, regTupleid);
              sqlite3VdbeAddOp3(v, OP_Copy, regRow, regCopy, nSortData - 1);
              sqlite3VdbeAddOp3(v, OP_MakeRecord, regCopy, nColumn + 1, 
regRow);
              sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, regRow);
@@ -2898,8 +2898,8 @@ generateOutputSubroutine(struct Parse *parse, 
struct Select *p,
      case SRT_EphemTab:{
              int regRec = sqlite3GetTempReg(parse);
              int regCopy = sqlite3GetTempRange(parse, in->nSdst + 1);
-            sqlite3VdbeAddOp3(v, OP_NextIdEphemeral, dest->iSDParm,
-                      in->nSdst, regCopy + in->nSdst);
+            sqlite3VdbeAddOp2(v, OP_NextIdEphemeral, dest->iSDParm,
+                      regCopy + in->nSdst);
              sqlite3VdbeAddOp3(v, OP_Copy, in->iSdst, regCopy,
                        in->nSdst - 1);
              sqlite3VdbeAddOp3(v, OP_MakeRecord, regCopy,
diff --git a/src/box/sql/tarantoolInt.h b/src/box/sql/tarantoolInt.h
index 94f94cb44..38f6a56c1 100644
--- a/src/box/sql/tarantoolInt.h
+++ b/src/box/sql/tarantoolInt.h
@@ -116,8 +116,14 @@ int tarantoolSqlite3EphemeralDelete(BtCursor * pCur);
  int tarantoolSqlite3EphemeralCount(BtCursor * pCur, i64 * pnEntry);
  int tarantoolSqlite3EphemeralDrop(BtCursor * pCur);
  int tarantoolSqlite3EphemeralClearTable(BtCursor * pCur);
-int tarantoolSqlite3EphemeralGetMaxId(BtCursor * pCur, uint32_t fieldno,
-                       uint64_t * max_id);
+/**
+ * Get next rowid for a cursor which points to an ephemeral table.
+ * @param bt_cur Cursor which will point to the new ephemeral space.
+ *
+ * @retval New unique rowid.
+ */
+uint64_t
+ephemeral_next_rowid(struct BtCursor *bt_cur);

  /**
   * Performs exactly as extract_key + sqlite3VdbeCompareMsgpack,
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 89c35d218..6ac12dec0 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -3790,27 +3790,19 @@ case OP_NextSequenceId: {
      break;
  }

-/* Opcode: NextIdEphemeral P1 P2 P3 * *
- * Synopsis: r[P3]=get_max(space_index[P1]{Column[P2]})
+/* Opcode: NextIdEphemeral P1 P2 * * *
+ * Synopsis: r[P2]=get_next_rowid(space_index[P1]})
   *
- * This opcode works in the same way as OP_NextId does, except it is
- * only applied for ephemeral tables. The difference is in the fact that
- * all ephemeral tables don't have space_id (to be more precise it 
equals to zero).
+ * This opcode stores next `rowid` for the ephemeral space to P2 register.
+ * `rowid` is necessary, because inserted to ephemeral space tuples may 
be not
+ * unique, while Tarantool`s spaces can contain only unique tuples.
   */
  case OP_NextIdEphemeral: {
      VdbeCursor *pC;
-    int p2;
      pC = p->apCsr[pOp->p1];
-    p2 = pOp->p2;
-    pOut = &aMem[pOp->p3];
-
-    assert(pC->uc.pCursor->curFlags & BTCF_TEphemCursor);
-
-    rc = tarantoolSqlite3EphemeralGetMaxId(pC->uc.pCursor, p2,
-                           (uint64_t *) &pOut->u.i);
-    if (rc) goto abort_due_to_error;
-
-    pOut->u.i += 1;
+    pOut = &aMem[pOp->p2];
+    struct BtCursor * bt_cur = pC->uc.pCursor;
+    pOut->u.i = ephemeral_next_rowid(bt_cur);
      pOut->flags = MEM_Int;
      break;
  }
diff --git a/src/box/sysview_engine.c b/src/box/sysview_engine.c
index 308a3328a..64fa6ee23 100644
--- a/src/box/sysview_engine.c
+++ b/src/box/sysview_engine.c
@@ -119,6 +119,14 @@ sysview_space_ephemeral_delete(struct space *space, 
const char *key)
      return -1;
  }

+static uint64_t
+sysview_space_ephemeral_next_rowid(struct space *space)
+{
+    (void)space;
+    unreachable();
+    return 0;
+}
+
  static void
  sysview_space_ephemeral_cleanup(struct space *space)
  {
@@ -206,6 +214,7 @@ static const struct space_vtab sysview_space_vtab = {
      /* .execute_upsert = */ sysview_space_execute_upsert,
      /* .ephemeral_replace = */ sysview_space_ephemeral_replace,
      /* .ephemeral_delete = */ sysview_space_ephemeral_delete,
+    /* .ephemeral_next_rowid = */ sysview_space_ephemeral_next_rowid,
      /* .ephemeral_cleanup = */ sysview_space_ephemeral_cleanup,
      /* .init_system_space = */ sysview_init_system_space,
      /* .init_ephemeral_space = */ sysview_init_ephemeral_space,
diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index b7238ae4c..b13b21dfb 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -2379,6 +2379,14 @@ vinyl_space_ephemeral_delete(struct space *space, 
const char *key)
      return -1;
  }

+static uint64_t
+vinyl_space_ephemeral_next_rowid(struct space *space)
+{
+    (void)space;
+    unreachable();
+    return 0;
+}
+
  static void
  vinyl_space_ephemeral_cleanup(struct space *space)
  {
@@ -4053,6 +4061,7 @@ static const struct space_vtab vinyl_space_vtab = {
      /* .execute_upsert = */ vinyl_space_execute_upsert,
      /* .ephemeral_replace = */ vinyl_space_ephemeral_replace,
      /* .ephemeral_delete = */ vinyl_space_ephemeral_delete,
+    /* .ephemeral_next_rowid = */ vinyl_space_ephemeral_next_rowid,
      /* .ephemeral_cleanup = */ vinyl_space_ephemeral_cleanup,
      /* .init_system_space = */ vinyl_init_system_space,
      /* .init_ephemeral_space = */ vinyl_init_ephemeral_space,
diff --git a/test/sql-tap/gh-3297_ephemeral_rowid.test.lua 
b/test/sql-tap/gh-3297_ephemeral_rowid.test.lua
new file mode 100755
index 000000000..d49baf300
--- /dev/null
+++ b/test/sql-tap/gh-3297_ephemeral_rowid.test.lua
@@ -0,0 +1,30 @@
+#!/usr/bin/env tarantool
+test = require("sqltester")
+test:plan(1)
+
+-- Check that OP_NextIdEphemeral generates unique ids.
+
+test:execsql [[
+    CREATE TABLE T1(A PRIMARY KEY);
+    CREATE TABLE T2(A PRIMARY KEY, B);
+    INSERT INTO T1 VALUES(12);
+    INSERT INTO T2 VALUES(1, 5);
+    INSERT INTO T2 VALUES(2, 2);
+    INSERT INTO T2 VALUES(3, 2);
+    INSERT INTO T2 VALUES(4, 2);
+]]
+
+test:do_execsql_test(
+    "gh-3297-1",
+    [[
+        SELECT * FROM ( SELECT A FROM T1 LIMIT 1), (SELECT B FROM T2 
LIMIT 10);
+    ]],
+    {
+        12, 2,
+        12, 2,
+        12, 2,
+        12, 5
+    }
+)
+
+test:finish_test()





More information about the Tarantool-patches mailing list