[tarantool-patches] Re: [PATCH v4 1/1] sql: return all generated ids via IPROTO

Imeev Mergen imeevma at tarantool.org
Wed Sep 26 19:08:06 MSK 2018


Hello! Thanks for review! Two patches placed below.


On 09/20/2018 07:36 PM, Vladislav Shpilevoy wrote:
> Hi! Thanks for the patch! See 10 comments below.
>
> On 17/09/2018 19:23, imeevma at tarantool.org wrote:
>> According to documentation some JDBC functions have an ability to
>> return all ids that were generated in executed INSERT statement.
>> This patch gives a way to implements such functinality.
>
> 1. Typos: 'way to implements', 'functinality'.
Fixed.
>
>>
>> Closes #2618
>> ---
>> Branch: 
>> https://github.com/tarantool/tarantool/tree/imeevma/gh-2618-return-all-generated-ids
>> Issue: https://github.com/tarantool/tarantool/issues/2618
>>
>>   src/box/execute.c        |  17 ++++-
>>   src/box/execute.h        |   1 +
>>   src/box/lua/net_box.c    |  16 ++++-
>>   src/box/sequence.c       |  37 ++++++++++
>>   src/box/sql/sqliteInt.h  |   4 ++
>>   src/box/sql/vdbeaux.c    |  29 ++++++++
>>   src/box/txn.c            |   5 ++
>>   src/box/txn.h            |   6 ++
>>   test/sql/errinj.result   |   3 +-
>>   test/sql/iproto.result   | 171 
>> ++++++++++++++++++++++++++++++++++++++---------
>>   test/sql/iproto.test.lua |  15 +++++
>>   11 files changed, 266 insertions(+), 38 deletions(-)
>>
>> diff --git a/src/box/execute.c b/src/box/execute.c
>> index 24459b4..8e4bc80 100644
>> --- a/src/box/execute.c
>> +++ b/src/box/execute.c
>> @@ -641,14 +641,27 @@ err:
>>               goto err;
>>           int changes = sqlite3_changes(db);
>>           int size = mp_sizeof_uint(SQL_INFO_ROW_COUNT) +
>> -               mp_sizeof_uint(changes);
>> +               mp_sizeof_uint(changes) +
>> +               mp_sizeof_uint(SQL_INFO_GENERATED_IDS) +
>> +               mp_sizeof_array(db->generated_ids_count);
>
> 2. When you use '<noun>_count', the noun should be in the singular.
Fixed.
>
>> +        for (uint64_t i = 0; i < db->generated_ids_count; ++i) {
>> +            size += db->generated_ids_array[i] >= 0 ?
>
> 3. Type can be omitted in a variable name (no <name>_array, but <name>s).
Fixed.
>
>> + mp_sizeof_uint(db->generated_ids_array[i]) :
>> +                mp_sizeof_int(db->generated_ids_array[i]);
>> +        }
>>           char *buf = obuf_alloc(out, size);
>>           if (buf == NULL) {
>>               diag_set(OutOfMemory, size, "obuf_alloc", "buf");
>> -            goto err;
>
> 4. Why?
I was wrong, fixed.
>
>>           }
>>           buf = mp_encode_uint(buf, SQL_INFO_ROW_COUNT);
>>           buf = mp_encode_uint(buf, changes);
>> +        buf = mp_encode_uint(buf, SQL_INFO_GENERATED_IDS);
>
> 5. I do not think we should send empty array. Maybe it is better to
> omit the key in such a case?
Done.
>
>> +        buf = mp_encode_array(buf, db->generated_ids_count);
>> +        for (uint64_t i = 0; i < db->generated_ids_count; ++i) {
>> +            buf = db->generated_ids_array[i] < 0 ?
>> +                  mp_encode_int(buf, db->generated_ids_array[i]) :
>> +                  mp_encode_uint(buf, db->generated_ids_array[i]);
>> +        }
>>       }
>>       iproto_reply_sql(out, &header_svp, response->sync, schema_version,
>>                keys);
>> diff --git a/src/box/sequence.c b/src/box/sequence.c
>> index 35b7605..234509c 100644
>> --- a/src/box/sequence.c
>> +++ b/src/box/sequence.c
>> @@ -178,6 +180,37 @@ sequence_update(struct sequence *seq, int64_t 
>> value)
>>       return 0;
>>   }
>>   +/*
>> + * Save generated value into txn.
>> + *
>> + * \param value value to save.
>> + * \retval 0 Success.
>> + * \retval -1 Error.
>> + */
>> +static inline int
>> +save_value(int64_t value)
>> +{
>> +    struct txn *txn = in_txn();
>> +    if (txn == NULL)
>> +        return 0;
>> +    if (txn->generated_ids.size == txn->generated_ids.capacity) {
>
> 6. Why do you store generated ids in box txn instead of vdbe? As I know,
> after commit/rollback txn object is freed so it does not matter where do
> you store ids array - on the heap or on a region - txn.generated_ids
> attribute will be invalid after commit/rollback. What is more, we do not
> need ids from non-SQL requests - they are never used.
>
> Last time we discussed that you should store result of sequence_next from
> OP_NextAutoincValue. And at the same place you can save the result into
> Vdbe generated ids array. This will work the same way as your current
> implementation, but will not slow down non-SQL requests.
Done.
>
>> +        txn->generated_ids.capacity = txn->generated_ids.capacity > 0 ?
>> +                          txn->generated_ids.capacity * 2 :
>> +                          SEQUENCE_GENERATED_IDS_MIN_LEN;
>> +        uint64_t capacity = txn->generated_ids.capacity *
>> +                    sizeof(*txn->generated_ids.array);
>> +        txn->generated_ids.array = realloc(txn->generated_ids.array,
>> +                           capacity);
>> +        if (txn->generated_ids.array == NULL) {
>> +            diag_set(OutOfMemory, txn->generated_ids.capacity,
>> +                 "realloc", "txn->generated_ids.array");
>> +            return -1;
>> +        }
>> +    }
>> +    txn->generated_ids.array[txn->generated_ids.size++] = value;
>> +    return 0;
>> +}
>> +
>>   int
>>   sequence_next(struct sequence *seq, int64_t *result)
>>   {
>> diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
>> index 1d32c9a..7d41ece 100644
>> --- a/src/box/sql/sqliteInt.h
>> +++ b/src/box/sql/sqliteInt.h
>> @@ -1525,6 +1525,10 @@ struct sqlite3 {
>>       u8 mTrace;        /* zero or more SQLITE_TRACE flags */
>>       u32 magic;        /* Magic number for detect library misuse */
>>       int nChange;        /* Value returned by sqlite3_changes() */
>> +    /* Number of generated ids. */
>> +    uint64_t generated_ids_count;
>> +    /* Array of generated ids. */
>> +    int64_t *generated_ids_array;
>
> 7. Do not store ids in multiple places, and moreover in sqlite3 that 
> will be
> removed in future. Use struct Vdbe. Ids array is per-request attribute.
Done.
>
>>       int nTotalChange;    /* Value returned by 
>> sqlite3_total_changes() */
>>       int aLimit[SQLITE_N_LIMIT];    /* Limits */
>>       int nMaxSorterMmap;    /* Maximum size of regions mapped by 
>> sorter */
>> diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
>> index 3b0c90c..7b5d6b9 100644
>> --- a/src/box/sql/vdbeaux.c
>> +++ b/src/box/sql/vdbeaux.c
>> @@ -2303,6 +2303,29 @@ sql_savepoint(Vdbe *p, const char *zName)
>>   }
>>     /*
>> + * Move array of generated ids into db.
>> + *
>> + * \param db db to save array to.
>> + * \retval 0 Success.
>> + * \retval -1 Error.
>
> 8. Use @, not \.
Fixed.
>
> 9. You should not have this function. Ids must be stored in Vdbe.
Done.
>
>> + */
>> +static inline int
>> +move_generated_ids_in_db(sqlite3 *db)
>> +{
>> +    struct txn *txn = in_txn();
>> +    if (txn == NULL)
>> +        return 0;
>> +    uint64_t size = txn->generated_ids.size *
>> +            sizeof(*txn->generated_ids.array);
>> +    db->generated_ids_count = txn->generated_ids.size;
>> +    db->generated_ids_array = malloc(size);
>> +    if (db->generated_ids_array == NULL)
>> +        return -1;
>> +    memcpy(db->generated_ids_array, txn->generated_ids.array, size);
>> +    return 0;
>> +}
>> +
>> +/*
>>    * This routine is called the when a VDBE tries to halt.  If the VDBE
>>    * has made changes and is in autocommit mode, then commit those
>>    * changes.  If a rollback is needed, then do the rollback.
> 10. I still think that we should not realloc a huge monolith array of 
> ids.
> My proposal is to use a list of small arrays. When a last chunk in the 
> list
> becomes full, we allocate a new chunk, append it to the list and use 
> it for
> next ids. But you can discuss it with Kirill if you do not agree.
Done.

New patch: sql: move autoincrement in vdbe

commit 45a5c014743f9c116c632fd5d6007114d6d45602
Author: Mergen Imeev <imeevma at gmail.com>
Date:   Thu Sep 20 21:54:32 2018 +0300

     sql: move autoincrement in vdbe

     This patch expands changes made in issue #2981. Now NULL in
     primary key always replaced by generated value during VDBE
     execution. It allows us to get all generated ids with ease.

     Part of #2618
     Closes #3670

diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 03f4e1b..987e3f4 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -655,9 +655,7 @@ sqlite3Insert(Parse * pParse,    /* Parser context */
              if (j < 0 || nColumn == 0
                  || (pColumn && j >= pColumn->nId)) {
                  if (i == (int) autoinc_fieldno) {
-                    sqlite3VdbeAddOp2(v,
-                              OP_NextAutoincValue,
-                              pTab->def->id,
+                    sqlite3VdbeAddOp2(v, OP_Null, 0,
                                iRegStore);
                      continue;
                  }
@@ -669,77 +667,35 @@ sqlite3Insert(Parse * pParse,    /* Parser context */
                                dflt,
                                iRegStore);
              } else if (useTempTable) {
-                if (i == (int) autoinc_fieldno) {
-                    int regTmp = ++pParse->nMem;
-                    /* Emit code which doesn't override
-                     * autoinc-ed value with select result
-                     * in case if result is NULL value.
-                     */
-                    sqlite3VdbeAddOp3(v, OP_Column, srcTab,
-                              j, regTmp);
-                    sqlite3VdbeAddOp2(v, OP_FCopy, regTmp,
-                              iRegStore);
-                    sqlite3VdbeChangeP3(v, -1,
-                                OPFLAG_SAME_FRAME |
-                                OPFLAG_NOOP_IF_NULL);
-                } else {
-                    sqlite3VdbeAddOp3(v, OP_Column, srcTab,
-                              j, iRegStore);
-                }
+                sqlite3VdbeAddOp3(v, OP_Column, srcTab,
+                          j, iRegStore);
              } else if (pSelect) {
                  if (regFromSelect != regData) {
-                    if (i == (int) autoinc_fieldno) {
-                        /* Emit code which doesn't override
-                         * autoinc-ed value with select result
-                         * in case that result is NULL
-                         */
-                        sqlite3VdbeAddOp2(v, OP_FCopy,
-                                  regFromSelect
-                                  + j,
-                                  iRegStore);
-                        sqlite3VdbeChangeP3(v, -1,
-                                    OPFLAG_SAME_FRAME
-                                    |
-                                    OPFLAG_NOOP_IF_NULL);
-                    } else {
-                        sqlite3VdbeAddOp2(v, OP_SCopy,
-                                  regFromSelect
-                                  + j,
-                                  iRegStore);
-                    }
+                    sqlite3VdbeAddOp2(v, OP_SCopy,
+                              regFromSelect + j,
+                              iRegStore);
                  }
              } else {
-
                  if (i == (int) autoinc_fieldno) {
                      if (pList->a[j].pExpr->op == TK_NULL) {
                          sqlite3VdbeAddOp2(v, OP_Null, 0, iRegStore);
                          continue;
                      }
-
-                    if (pList->a[j].pExpr->op ==
-                        TK_REGISTER) {
-                        /* Emit code which doesn't override
-                         * autoinc-ed value with select result
-                         * in case that result is NULL
-                         */
-                        sqlite3VdbeAddOp2(v, OP_FCopy,
-                                  pList->a[j].
-                                  pExpr->iTable,
-                                  iRegStore);
-                        sqlite3VdbeChangeP3(v, -1,
-                                    OPFLAG_SAME_FRAME
-                                    |
-                                    OPFLAG_NOOP_IF_NULL);
-                        continue;
-                    }
                  }
-
                  sqlite3ExprCode(pParse, pList->a[j].pExpr,
                          iRegStore);
              }
          }

          /*
+         * Replace NULL in PK with autoincrement by
+         * generated value.
+         */
+        if ((int) autoinc_fieldno >= 0) {
+            sqlite3VdbeAddOp2(v, OP_NextAutoincValue, pTab->def->id,
+                      regData + (int) autoinc_fieldno);
+        }
+        /*
           * Generate code to check constraints and process
           * final insertion.
           */
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 00ffb0c..3d0548b 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1156,6 +1156,10 @@ case OP_NextAutoincValue: {
      assert(pOp->p1 > 0);
      assert(pOp->p2 > 0);

+    pIn2 = &p->aMem[pOp->p2];
+    if ((pIn2->flags & MEM_Null) == 0)
+        break;
+
      struct space *space = space_by_id(pOp->p1);
      if (space == NULL) {
          rc = SQL_TARANTOOL_ERROR;
@@ -3730,44 +3734,6 @@ case OP_NextIdEphemeral: {
      break;
  }

-/* Opcode: FCopy P1 P2 P3 * *
- * Synopsis: reg[P2 at cur_frame]= reg[P1 at root_frame(OPFLAG_SAME_FRAME)]
- *
- * Copy integer value of register P1 in root frame in to register P2 of 
current
- * frame. If current frame is topmost - copy within signle frame.
- * Source register must hold integer value.
- *
- * If P3's flag OPFLAG_SAME_FRAME is set, do shallow copy of register 
within
- * same frame, still making sure the value is integer.
- *
- * If P3's flag OPFLAG_NOOP_IF_NULL is set, then do nothing if reg[P1] 
is NULL
- */
-case OP_FCopy: {     /* out2 */
-    VdbeFrame *pFrame;
-    Mem *pIn1, *pOut;
-    if (p->pFrame && ((pOp->p3 & OPFLAG_SAME_FRAME) == 0)) {
-        for(pFrame=p->pFrame; pFrame->pParent; pFrame=pFrame->pParent);
-        pIn1 = &pFrame->aMem[pOp->p1];
-    } else {
-        pIn1 = &aMem[pOp->p1];
-    }
-
-    if ((pOp->p3 & OPFLAG_NOOP_IF_NULL) && (pIn1->flags & MEM_Null)) {
-        pOut = &aMem[pOp->p2];
-        if (pOut->flags & MEM_Undefined) pOut->flags = MEM_Null;
-        /* Flag is set and register is NULL -> do nothing  */
-    } else {
-        assert(memIsValid(pIn1));
-        assert(pIn1->flags &  MEM_Int);
-
-        pOut = &aMem[pOp->p2];
-        MemSetTypeFlag(pOut, MEM_Int);
-
-        pOut->u.i = pIn1->u.i;
-    }
-    break;
-}
-
  /* Opcode: Delete P1 P2 P3 P4 P5
   *
   * Delete the record at which the P1 cursor is currently pointing.
diff --git a/test/sql/gh-2981-check-autoinc.result 
b/test/sql/gh-2981-check-autoinc.result
index b0f55e6..40bb71c 100644
--- a/test/sql/gh-2981-check-autoinc.result
+++ b/test/sql/gh-2981-check-autoinc.result
@@ -19,6 +19,9 @@ box.sql.execute("CREATE TABLE t2 (s1 INTEGER PRIMARY 
KEY AUTOINCREMENT, s2 INTEG
  box.sql.execute("CREATE TABLE t3 (s1 INTEGER PRIMARY KEY 
AUTOINCREMENT, s2 INTEGER, CHECK (s1 < 10));");
  ---
  ...
+box.sql.execute("CREATE TABLE t4 (s1 INTEGER PRIMARY KEY AUTOINCREMENT, 
s2 INTEGER, CHECK (s1 <> 19));");
+---
+...
  box.sql.execute("insert into t1 values (18, null);")
  ---
  ...
@@ -47,6 +50,13 @@ box.sql.execute("insert into t3(s2) values (null)")
  ---
  - error: 'CHECK constraint failed: T3'
  ...
+box.sql.execute("insert into t4 values (18, null);")
+---
+...
+box.sql.execute("insert into t4 values (null, null);")
+---
+- error: 'CHECK constraint failed: T4'
+...
  box.sql.execute("DROP TABLE t1")
  ---
  ...
@@ -56,3 +66,35 @@ box.sql.execute("DROP TABLE t2")
  box.sql.execute("DROP TABLE t3")
  ---
  ...
+box.sql.execute("DROP TABLE t4")
+---
+...
+--
+-- gh-3670: Assertion with large number in autoincrement column.
+--
+box.sql.execute("CREATE TABLE t5 (s1 INTEGER PRIMARY KEY AUTOINCREMENT);")
+---
+...
+box.sql.execute("insert into t5 values (2147483647);")
+---
+...
+box.sql.execute("insert into t5 select max(s1)*max(s1)*max(s1) from t5;")
+---
+- error: datatype mismatch
+...
+box.sql.execute("CREATE TABLE t6 (s1 INTEGER PRIMARY KEY AUTOINCREMENT, 
s2 CHAR);")
+---
+...
+box.sql.execute("insert into t6 values (1, 'a');")
+---
+...
+box.sql.execute("insert into t6 select s2, s2 from t6;")
+---
+- error: datatype mismatch
+...
+box.sql.execute("DROP TABLE t5")
+---
+...
+box.sql.execute("DROP TABLE t6")
+---
+...
diff --git a/test/sql/gh-2981-check-autoinc.test.lua 
b/test/sql/gh-2981-check-autoinc.test.lua
index 98a5fb4..6fabf0e 100644
--- a/test/sql/gh-2981-check-autoinc.test.lua
+++ b/test/sql/gh-2981-check-autoinc.test.lua
@@ -7,6 +7,7 @@ box.cfg{}
  box.sql.execute("CREATE TABLE t1 (s1 INTEGER PRIMARY KEY 
AUTOINCREMENT, s2 INTEGER, CHECK (s1 <> 19));");
  box.sql.execute("CREATE TABLE t2 (s1 INTEGER PRIMARY KEY 
AUTOINCREMENT, s2 INTEGER, CHECK (s1 <> 19 AND s1 <> 25));");
  box.sql.execute("CREATE TABLE t3 (s1 INTEGER PRIMARY KEY 
AUTOINCREMENT, s2 INTEGER, CHECK (s1 < 10));");
+box.sql.execute("CREATE TABLE t4 (s1 INTEGER PRIMARY KEY AUTOINCREMENT, 
s2 INTEGER, CHECK (s1 <> 19));");

  box.sql.execute("insert into t1 values (18, null);")
  box.sql.execute("insert into t1(s2) values (null);")
@@ -19,7 +20,24 @@ box.sql.execute("insert into t2(s2) values (null);")
  box.sql.execute("insert into t3 values (9, null)")
  box.sql.execute("insert into t3(s2) values (null)")

+box.sql.execute("insert into t4 values (18, null);")
+box.sql.execute("insert into t4 values (null, null);")
+
  box.sql.execute("DROP TABLE t1")
  box.sql.execute("DROP TABLE t2")
  box.sql.execute("DROP TABLE t3")
+box.sql.execute("DROP TABLE t4")
+
+--
+-- gh-3670: Assertion with large number in autoincrement column.
+--
+box.sql.execute("CREATE TABLE t5 (s1 INTEGER PRIMARY KEY AUTOINCREMENT);")
+box.sql.execute("insert into t5 values (2147483647);")
+box.sql.execute("insert into t5 select max(s1)*max(s1)*max(s1) from t5;")
+
+box.sql.execute("CREATE TABLE t6 (s1 INTEGER PRIMARY KEY AUTOINCREMENT, 
s2 CHAR);")
+box.sql.execute("insert into t6 values (1, 'a');")
+box.sql.execute("insert into t6 select s2, s2 from t6;")

+box.sql.execute("DROP TABLE t5")
+box.sql.execute("DROP TABLE t6")

New patch: sql: return all generated ids via IPROTO

commit eba2e082f177a6c557731645a40661dce2422d14
Author: Mergen Imeev <imeevma at gmail.com>
Date:   Mon Sep 24 22:28:43 2018 +0300

     sql: return all generated ids via IPROTO

     According to documentation some JDBC functions have an ability to
     return all ids that were generated in executed INSERT statement.
     This patch gives a way to implement such functionality.

     Closes #2618

diff --git a/src/box/execute.c b/src/box/execute.c
index 24459b4..f06e4fb 100644
--- a/src/box/execute.c
+++ b/src/box/execute.c
@@ -42,6 +42,7 @@
  #include "schema.h"
  #include "port.h"
  #include "tuple.h"
+#include "sql/vdbeInt.h"

  const char *sql_type_strs[] = {
      NULL,
@@ -640,13 +641,45 @@ err:
          if (iproto_reply_map_key(out, 1, IPROTO_SQL_INFO) != 0)
              goto err;
          int changes = sqlite3_changes(db);
+        struct rlist *id_list = &((struct Vdbe *)stmt)->generated_ids;
+        uint64_t count = 0;
          int size = mp_sizeof_uint(SQL_INFO_ROW_COUNT) +
                 mp_sizeof_uint(changes);
+        if (rlist_empty(id_list) == 0) {
+            struct id_array *id_array;
+            rlist_foreach_entry(id_array, id_list, link) {
+                int64_t *ids = id_array->ids;
+                for (uint64_t i = 0; i < id_array->count; ++i) {
+                    size += ids[i] >= 0 ?
+                        mp_sizeof_uint(ids[i]) :
+                        mp_sizeof_int(ids[i]);
+                }
+                count += id_array->count;
+            }
+        }
+        if (count > 0) {
+            size += mp_sizeof_uint(SQL_INFO_GENERATED_IDS) +
+                mp_sizeof_array(count);
+        }
+
          char *buf = obuf_alloc(out, size);
          if (buf == NULL) {
              diag_set(OutOfMemory, size, "obuf_alloc", "buf");
              goto err;
          }
+        if (count > 0) {
+            buf = mp_encode_uint(buf, SQL_INFO_GENERATED_IDS);
+            buf = mp_encode_array(buf, count);
+            struct id_array *id_array;
+            rlist_foreach_entry(id_array, id_list, link) {
+                int64_t *ids = id_array->ids;
+                for (uint64_t i = 0; i < id_array->count; ++i) {
+                    buf = ids[i] >= 0 ?
+                        mp_encode_uint(buf, ids[i]) :
+                        mp_encode_int(buf, ids[i]);
+                }
+            }
+        }
          buf = mp_encode_uint(buf, SQL_INFO_ROW_COUNT);
          buf = mp_encode_uint(buf, changes);
      }
diff --git a/src/box/execute.h b/src/box/execute.h
index f21393b..614d3d0 100644
--- a/src/box/execute.h
+++ b/src/box/execute.h
@@ -42,6 +42,7 @@ extern "C" {
  /** Keys of IPROTO_SQL_INFO map. */
  enum sql_info_key {
      SQL_INFO_ROW_COUNT = 0,
+    SQL_INFO_GENERATED_IDS = 1,
      sql_info_key_MAX,
  };

diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c
index a928a4c..9f4088a 100644
--- a/src/box/lua/net_box.c
+++ b/src/box/lua/net_box.c
@@ -668,14 +668,30 @@ static void
  netbox_decode_sql_info(struct lua_State *L, const char **data)
  {
      uint32_t map_size = mp_decode_map(data);
-    /* Only SQL_INFO_ROW_COUNT is available. */
      assert(map_size == 1);
      (void) map_size;
+    lua_newtable(L);
      uint32_t key = mp_decode_uint(data);
+    /*
+     * If SQL_INFO_GENERATED_IDS in data then it should be
+     * just before SQL_INFO_ROW_COUNT.
+     */
+    if (key == SQL_INFO_GENERATED_IDS) {
+        uint64_t count = mp_decode_array(data);
+        assert (count > 0);
+        lua_createtable(L, 0, count);
+        lua_setfield(L, -2, "generated_ids");
+        lua_getfield(L, -1, "generated_ids");
+        for (uint32_t j = 0; j < count; ++j) {
+            int64_t value = mp_decode_uint(data);
+            lua_pushinteger(L, value);
+            lua_rawseti(L, -2, j + 1);
+        }
+        lua_pop(L, 1);
+        key = mp_decode_uint(data);
+    }
      assert(key == SQL_INFO_ROW_COUNT);
-    (void) key;
      uint32_t row_count = mp_decode_uint(data);
-    lua_createtable(L, 0, 1);
      lua_pushinteger(L, row_count);
      lua_setfield(L, -2, "rowcount");
  }
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 3d0548b..53544fc 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1177,6 +1177,22 @@ case OP_NextAutoincValue: {
      pOut->flags = MEM_Int;
      pOut->u.i = value;

+    /* Save new id to return it via IProto. */
+    struct id_array *id_array;
+    if (rlist_empty(&p->generated_ids) != 0) {
+        id_array = malloc(sizeof(*id_array));
+        memset(id_array, 0, sizeof(*id_array));
+        rlist_add_entry(&p->generated_ids, id_array, link);
+    } else {
+        id_array = rlist_last_entry(&p->generated_ids, struct id_array,
+                        link);
+    }
+    if (id_array->count == GENERATED_ID_ARRAY_SIZE) {
+        id_array = malloc(sizeof(*id_array));
+        memset(id_array, 0, sizeof(*id_array));
+        rlist_add_entry(&p->generated_ids, id_array, link);
+    }
+    id_array->ids[id_array->count++] = value;
      break;
  }

diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index ce97f49..95f6c60 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -331,6 +331,25 @@ struct ScanStatus {
      char *zName;        /* Name of table or index */
  };

+enum
+{
+    /** Capacity of array of ids. */
+    GENERATED_ID_ARRAY_SIZE = 16,
+};
+
+/**
+ * An element of list of generated ids.
+ */
+struct id_array
+{
+    /** A link in a generated id array list. */
+    struct rlist link;
+    /** Number of elements in array. */
+    uint64_t count;
+    /** Array of generated ids. */
+    int64_t ids[GENERATED_ID_ARRAY_SIZE];
+};
+
  /*
   * An instance of the virtual machine.  This structure contains the 
complete
   * state of the virtual machine.
@@ -355,6 +374,9 @@ struct Vdbe {
      i64 nFkConstraint;    /* Number of imm. FK constraints this VM */
      uint32_t schema_ver;    /* Schema version at the moment of VDBE 
creation. */

+    /* List of arrays of generated ids. */
+    struct rlist generated_ids;
+
      /*
       * In recursive triggers we can execute INSERT/UPDATE OR IGNORE
       * statements. If IGNORE error action happened inside a trigger,
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 54edf1b..a177a18 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -57,6 +57,8 @@ sqlite3VdbeCreate(Parse * pParse)
          return 0;
      memset(p, 0, sizeof(Vdbe));
      p->db = db;
+    /* Init list of generated ids. */
+    rlist_create(&p->generated_ids);
      if (db->pVdbe) {
          db->pVdbe->pPrev = p;
      }
@@ -2746,6 +2748,12 @@ sqlite3VdbeClearObject(sqlite3 * db, Vdbe * p)
      vdbeFreeOpArray(db, p->aOp, p->nOp);
      sqlite3DbFree(db, p->aColName);
      sqlite3DbFree(db, p->zSql);
+    while (rlist_empty(&p->generated_ids) == 0) {
+        struct id_array *id_array =
+            rlist_shift_entry(&p->generated_ids, struct id_array,
+                      link);
+        free(id_array);
+    }
  #ifdef SQLITE_ENABLE_STMT_SCANSTATUS
      {
          int i;
diff --git a/test/sql/iproto.result b/test/sql/iproto.result
index af474bc..70cca32 100644
--- a/test/sql/iproto.result
+++ b/test/sql/iproto.result
@@ -580,6 +580,86 @@ cn:close()
  box.sql.execute('drop table test')
  ---
  ...
+-- gh-2618 Return generated columns after INSERT in IPROTO.
+-- Return all ids generated in current INSERT statement.
+box.sql.execute('create table test (id integer primary key 
autoincrement, a integer)')
+---
+...
+cn = remote.connect(box.cfg.listen)
+---
+...
+cn:execute('insert into test values (1, 1)')
+---
+- rowcount: 1
+...
+cn:execute('insert into test values (null, 2)')
+---
+- generated_ids:
+  - 2
+  rowcount: 1
+...
+cn:execute('update test set a = 11 where id == 1')
+---
+- rowcount: 1
+...
+cn:execute('insert into test values (100, 1), (null, 1), (120, 1), 
(null, 1)')
+---
+- generated_ids:
+  - 101
+  - 121
+  rowcount: 4
+...
+cn:execute('insert into test values (null, 1), (null, 1), (null, 1), 
(null, 1), (null, 1)')
+---
+- generated_ids:
+  - 122
+  - 123
+  - 124
+  - 125
+  - 126
+  rowcount: 5
+...
+cn:execute('replace into test values (null, 1), (null, 1)')
+---
+- generated_ids:
+  - 127
+  - 128
+  rowcount: 2
+...
+cn:execute('select * from test')
+---
+- metadata:
+  - name: ID
+  - name: A
+  rows:
+  - [1, 11]
+  - [2, 2]
+  - [100, 1]
+  - [101, 1]
+  - [120, 1]
+  - [121, 1]
+  - [122, 1]
+  - [123, 1]
+  - [124, 1]
+  - [125, 1]
+  - [126, 1]
+  - [127, 1]
+  - [128, 1]
+...
+cn:execute('create table test2 (id integer primary key, a integer)')
+---
+- rowcount: 1
+...
+cn:execute('drop table test2')
+---
+- rowcount: 1
+...
+cn:close()
+---
+...
+box.sql.execute('drop table test')
+---
+...
  box.schema.user.revoke('guest', 'read,write,execute', 'universe')
  ---
  ...
diff --git a/test/sql/iproto.test.lua b/test/sql/iproto.test.lua
index 220331b..fea51c9 100644
--- a/test/sql/iproto.test.lua
+++ b/test/sql/iproto.test.lua
@@ -201,6 +201,22 @@ future4:wait_result()
  cn:close()
  box.sql.execute('drop table test')

+-- gh-2618 Return generated columns after INSERT in IPROTO.
+-- Return all ids generated in current INSERT statement.
+box.sql.execute('create table test (id integer primary key 
autoincrement, a integer)')
+cn = remote.connect(box.cfg.listen)
+cn:execute('insert into test values (1, 1)')
+cn:execute('insert into test values (null, 2)')
+cn:execute('update test set a = 11 where id == 1')
+cn:execute('insert into test values (100, 1), (null, 1), (120, 1), 
(null, 1)')
+cn:execute('insert into test values (null, 1), (null, 1), (null, 1), 
(null, 1), (null, 1)')
+cn:execute('replace into test values (null, 1), (null, 1)')
+cn:execute('select * from test')
+cn:execute('create table test2 (id integer primary key, a integer)')
+cn:execute('drop table test2')
+cn:close()
+box.sql.execute('drop table test')
+
  box.schema.user.revoke('guest', 'read,write,execute', 'universe')
  space = nil





More information about the Tarantool-patches mailing list