Tarantool development patches archive
 help / color / mirror / Atom feed
From: Imeev Mergen <imeevma@tarantool.org>
To: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>,
	tarantool-patches@freelists.org
Subject: [tarantool-patches] Re: [PATCH v2 1/1] sql: return last_insert_id via IPROTO
Date: Mon, 13 Aug 2018 15:34:08 +0300	[thread overview]
Message-ID: <dee3598b-468a-db5b-e19e-d01346018980@tarantool.org> (raw)
In-Reply-To: <ff67f92c-c17b-7477-4215-c67f9f8af4b0@tarantool.org>

Hi! Thank you for the review!


On 08/06/2018 06:24 PM, Vladislav Shpilevoy wrote:
> Hi! Thanks for the patch! See 11 comments below.
>
> On 03/08/2018 13:28, imeevma@tarantool.org wrote:
>> After this patch client will get last_insert_id
>> as additional metadata when he executes some
>> queries.
>>
>> Part of #2618.
>>
>> @TarantoolBot document
>
> 1. Please, describe in the same request new iproto key
> you have introduced.
Done.
>
>> Title: SQL function last_insert_id.
>> Function last_insert_id returns first autogenerated ID in
>> last INSERT/REPLACE statement in current session. Return
>> value of function is undetermined when more than one
>> INSERT/REPLACE statements executed asynchronously.
>> Example:
>> CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, a INTEGER);
>> INSERT INTO test VALUES (NULL, 1);
>> SELECT last_insert_id();
>> ---
>> Branch: 
>> https://github.com/tarantool/tarantool/tree/imeevma/gh-2618-return-generated-columns-and-values
>> Issue: https://github.com/tarantool/tarantool/issues/2618
>>
>>   src/box/execute.c             |  11 ++-
>>   src/box/execute.h             |   1 +
>>   src/box/lua/net_box.c         |  12 ++-
>>   src/box/sequence.c            |  17 ++++
>>   src/box/sequence.h            |   9 +++
>>   src/box/session.cc            |   1 +
>>   src/box/session.h             |   2 +
>>   src/box/sql.c                 |   1 +
>>   src/box/sql/func.c            |  18 +++++
>>   src/box/sql/vdbe.c            |  33 ++++++++
>>   test/sql-tap/insert3.test.lua |  27 ++++++-
>>   test/sql/errinj.result        |   3 +-
>>   test/sql/iproto.result        | 180 
>> ++++++++++++++++++++++++++++++++++--------
>>   test/sql/iproto.test.lua      |  19 +++++
>>   14 files changed, 296 insertions(+), 38 deletions(-)
>>
>> diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c
>> index 308c9c7..0e24b47 100644
>> --- a/src/box/lua/net_box.c
>> +++ b/src/box/lua/net_box.c
>> @@ -667,16 +667,22 @@ 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;
>>       uint32_t 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);
>> +    key = mp_decode_uint(data);
>> +    assert(key == SQL_INFO_LAST_INSERT_ID);
>> +    (void) key;
>
> 2. Now the key is used and its (void) guard can be removed,
> it is not?
Fixed.
>
>> +    int64_t last_insert_id = mp_typeof(**data) == MP_UINT ?
>> +                 (int64_t) mp_decode_uint(data) :
>> +                 mp_decode_int(data);
>> +    lua_createtable(L, 0, 2);
>>       lua_pushinteger(L, row_count);
>>       lua_setfield(L, -2, "rowcount");
>> +    lua_pushinteger(L, last_insert_id);
>> +    lua_setfield(L, -2, "last_insert_id");
>>   }
>>     static int
>> diff --git a/src/box/sequence.c b/src/box/sequence.c
>> index 35b7605..cefb3db 100644
>> --- a/src/box/sequence.c
>> +++ b/src/box/sequence.c
>> @@ -340,3 +340,20 @@ sequence_data_iterator_create(void)
>>       light_sequence_iterator_freeze(&sequence_data_index, &iter->iter);
>>       return &iter->base;
>>   }
>> +
>> +int64_t
>> +sequence_get_value(struct sequence *seq)
>> +{
>> +    uint32_t key = seq->def->id;
>> +    uint32_t hash = sequence_hash(key);
>> +    uint32_t pos = light_sequence_find_key(&sequence_data_index, 
>> hash, key);
>> +    /*
>> +     * Number 1 is often used as start value of sequences in
>> +     * general. We are goint to use it as default value.
>> +     */
>> +    if (pos == light_sequence_end)
>> +        return 1;
>
> 3. The same comment as in the previous review. 1 is not mandatory start
> value and can be different in a common case, as well as 0, -1, 2 and any
> other constant. Use sequence_def.start.
Function was removed as it isn't necessary anymore.
>
> 4. Please, when sending a new patch version, explicitly answer my
> comments either in a new version email or in a response to my email. It
> is hard to iterate through two emails (with the comments and with your
> patch) at the same time.
Ok, understood.
>
>> +    struct sequence_data data = 
>> light_sequence_get(&sequence_data_index,
>> +                               pos);
>> +    return data.value;
>> +}
>> diff --git a/src/box/sql.c b/src/box/sql.c
>> index 2b93c3d..d9752f3 100644
>> --- a/src/box/sql.c
>> +++ b/src/box/sql.c
>> @@ -48,6 +48,7 @@
>>   #include "txn.h"
>>   #include "space.h"
>>   #include "space_def.h"
>> +#include "sequence.h"
>
> 5. Why?
Fixed.
>
>>   #include "index_def.h"
>>   #include "tuple.h"
>>   #include "fiber.h"
>> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
>> index 45056a7..eca69b7 100644
>> --- a/src/box/sql/func.c
>> +++ b/src/box/sql/func.c
>> @@ -38,6 +38,7 @@
>>   #include "vdbeInt.h"
>>   #include "version.h"
>>   #include "coll.h"
>> +#include "src/box/session.h"
>
> 6. For box/ dir we do not use src/ prefix.
> Please, use 'box/session.h'.
Fixed.
>
>>   #include <unicode/ustring.h>
>>   #include <unicode/ucasemap.h>
>>   #include <unicode/ucnv.h>
>> @@ -601,6 +602,22 @@ changes(sqlite3_context * context, int NotUsed, 
>> sqlite3_value ** NotUsed2)
>>   }
>>     /*
>> + * Implementation of the last_insert_id() function. Returns first
>
> 7. As I said earlier, please, do not describe the function in a
> comment like it is an object. Use imperative. I see that other
> functions in the file use the same style, but do not follow it -
> this style is sqlite's obsolete one.
Fixed.
>
>> + * autogenerated ID in last INSERT/REPLACE in current session.
>> + *
>> + * @param context Context being used.
>> + * @param not_used Unused.
>> + * @param not_used2 Unused.
>> + */
>> +static void
>> +last_insert_id(sqlite3_context *context, int not_used,
>> +           sqlite3_value **not_used2)
>> +{
>> +    UNUSED_PARAMETER2(not_used, not_used2);
>> +    sqlite3_result_int(context, current_session()->sql_last_insert_id);
>> +}
>> +
>> +/*
>>    * Implementation of the total_changes() SQL function.  The return 
>> value is
>>    * the same as the sqlite3_total_changes() API function.
>>    */
>> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
>> index ca89908..38cd501 100644
>> --- a/src/box/sql/vdbe.c
>> +++ b/src/box/sql/vdbe.c
>> @@ -567,6 +567,30 @@ out2Prerelease(Vdbe *p, VdbeOp *pOp)
>>       }
>>   }
>>   +/**
>> + * Check that new ID is autogenerated in current query. If it is
>> + * this function sets sql_last_insert_id of current session as
>> + * first autogenerated ID in last INSERT/REPLACE query executed
>> + * in current session.
>> + *
>> + * @param space space to get sequence from.
>> + * @retval true new id autogenerated.
>> + * @retval false no new id autogenerated.
>> + */
>> +static inline bool
>> +check_last_insert_id(struct space *space)
>> +{
>> +    int64_t new_id = 0;
>> +    struct session *session = current_session();
>> +    struct sequence *sequence = space->sequence;
>> +    if (sequence != NULL)
>> +        new_id = sequence_get_value(sequence);
>
> 8. If sequence == NULL, this function should not do anything,
> it is not? I think, it is simpler to just inline it and
> remove some of checks. See the comment 10.
Unnecessary as way last_insert_id() works changed.
>
>> +    if (session->sql_last_insert_id == new_id)
>> +        return false;
>> +    session->sql_last_insert_id = new_id;
>> +    return true;
>> +}
>> +
>>   /*
>>    * Execute as much of a VDBE program as we can.
>>    * This is the core of sqlite3_step().
>> @@ -599,6 +623,11 @@ int sqlite3VdbeExec(Vdbe *p)
>>       u64 start;                 /* CPU clock count at start of 
>> opcode */
>>   #endif
>>       struct session *user_session = current_session();
>> +    /*
>> +     * Field sql_last_insert_id of current session should be
>> +     * set no more than once for each query.
>> +     */
>> +    bool last_insert_id_is_set = false;
>
> 9. For flags we use 'is_' prefix.
Fixed.
>
>>       /*** INSERT STACK UNION HERE ***/
>>         assert(p->magic==VDBE_MAGIC_RUN);  /* sqlite3_step() verifies 
>> this */
>> @@ -4300,6 +4329,10 @@ case OP_IdxInsert: {        /* in2 */
>>                   rc = tarantoolSqlite3Replace(pBtCur->space,
>>                                    pIn2->z,
>>                                    pIn2->z + pIn2->n);
>> +            if (rc == 0 && !last_insert_id_is_set) {
>> +                last_insert_id_is_set =
>> +                    check_last_insert_id(pBtCur->space);
>
> 10. I propose to rewrite it as
>
> if (!last_insert_id_is_set && rc == 0 && space->sequence != NULL) {
>     last_insert_id_is_set = true;
>     user_session->sql_last_insert_id =
>         sequence_get_value(space->sequence);
> }
>
> And remove check_last_insert_id.
Partially fixed.
>
>> +            }
>>           } else if (pBtCur->curFlags & BTCF_TEphemCursor) {
>>               rc = tarantoolSqlite3EphemeralInsert(pBtCur->space,
>>                                    pIn2->z,> @@ -580,6 +612,90 @@ 
>> cn:close()
>>   box.sql.execute('drop table test')
>>   ---
>>   ...
>> +-- gh-2618 Return generated columns after INSERT in IPROTO.
>> +-- Return autogenerated id of first inserted tuple in last insert.
>
> 11. 'In last insert' of what or whither?
Fixed.

commit 2b292311e61f3c25a4c776bd75e3e6d241ff66a1
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Tue Jul 31 16:43:38 2018 +0300

     sql: return last_insert_id via IPROTO

     After this patch client will get last_insert_id
     as additional metadata when he executes some
     queries.

     Part of #2618.

     @TarantoolBot document
     Title: SQL function last_insert_id()
     and IPROTO key last_insert_id.
     Function last_insert_id() returns first primary key value
     autogenerated in last INSERT/REPLACE statement in current
     session. Return value of function is undetermined when more
     than one INSERT/REPLACE statements executed asynchronously.
     IPROTO key last_insert_id is a metadata returned through
     IPROTO after such queries as INSERT, REPLACE, UPDATE etc.
     Value of this key is equal to value returned by function
     last_insert_id() executed after the query.
     Example:
     CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, a INTEGER);
     INSERT INTO test VALUES (NULL, 1);
     SELECT last_insert_id();

diff --git a/src/box/execute.c b/src/box/execute.c
index 24459b4..e2eabc7 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 "session.h"

  const char *sql_type_strs[] = {
      NULL,
@@ -640,8 +641,13 @@ err:
          if (iproto_reply_map_key(out, 1, IPROTO_SQL_INFO) != 0)
              goto err;
          int changes = sqlite3_changes(db);
+        int64_t last_insert_id = current_session()->sql_last_insert_id;
          int size = mp_sizeof_uint(SQL_INFO_ROW_COUNT) +
-               mp_sizeof_uint(changes);
+               mp_sizeof_uint(changes) +
+               mp_sizeof_uint(SQL_INFO_LAST_INSERT_ID) +
+               (last_insert_id >= 0 ?
+                mp_sizeof_uint(last_insert_id) :
+                mp_sizeof_int(last_insert_id));
          char *buf = obuf_alloc(out, size);
          if (buf == NULL) {
              diag_set(OutOfMemory, size, "obuf_alloc", "buf");
@@ -649,6 +655,9 @@ err:
          }
          buf = mp_encode_uint(buf, SQL_INFO_ROW_COUNT);
          buf = mp_encode_uint(buf, changes);
+        buf = mp_encode_uint(buf, SQL_INFO_LAST_INSERT_ID);
+        buf = last_insert_id < 0 ? mp_encode_int(buf, last_insert_id) :
+              mp_encode_uint(buf, last_insert_id);
      }
      iproto_reply_sql(out, &header_svp, response->sync, schema_version,
               keys);
diff --git a/src/box/execute.h b/src/box/execute.h
index f21393b..ead09fc 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_LAST_INSERT_ID = 1,
      sql_info_key_MAX,
  };

diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c
index 308c9c7..83ba9b8 100644
--- a/src/box/lua/net_box.c
+++ b/src/box/lua/net_box.c
@@ -667,16 +667,21 @@ 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;
      uint32_t 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);
+    key = mp_decode_uint(data);
+    assert(key == SQL_INFO_LAST_INSERT_ID);
+    int64_t last_insert_id = mp_typeof(**data) == MP_UINT ?
+                 (int64_t) mp_decode_uint(data) :
+                 mp_decode_int(data);
+    lua_createtable(L, 0, 2);
      lua_pushinteger(L, row_count);
      lua_setfield(L, -2, "rowcount");
+    lua_pushinteger(L, last_insert_id);
+    lua_setfield(L, -2, "last_insert_id");
  }

  static int
diff --git a/src/box/session.cc b/src/box/session.cc
index 64714cd..2b8dab9 100644
--- a/src/box/session.cc
+++ b/src/box/session.cc
@@ -108,6 +108,7 @@ session_create(enum session_type type)
      session->type = type;
      session->sql_flags = default_flags;
      session->sql_default_engine = SQL_STORAGE_ENGINE_MEMTX;
+    session->sql_last_insert_id = 0;

      /* For on_connect triggers. */
      credentials_init(&session->credentials, guest_user->auth_token,
diff --git a/src/box/session.h b/src/box/session.h
index df1dcbc..3d4049c 100644
--- a/src/box/session.h
+++ b/src/box/session.h
@@ -92,6 +92,8 @@ union session_meta {
  struct session {
      /** Session id. */
      uint64_t id;
+    /** First PK autogenerated in last INSERT/REPLACE query */
+    int64_t sql_last_insert_id;
      /** SQL Tarantool Default storage engine. */
      uint8_t sql_default_engine;
      /** SQL Connection flag for current user session */
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 45056a7..164bb6e 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -38,6 +38,7 @@
  #include "vdbeInt.h"
  #include "version.h"
  #include "coll.h"
+#include "box/session.h"
  #include <unicode/ustring.h>
  #include <unicode/ucasemap.h>
  #include <unicode/ucnv.h>
@@ -601,6 +602,22 @@ changes(sqlite3_context * context, int NotUsed, 
sqlite3_value ** NotUsed2)
  }

  /*
+ * Return first PK autogenerated in last INSERT/REPLACE in which
+ * PK was generated in current session.
+ *
+ * @param context Context being used.
+ * @param not_used Unused.
+ * @param not_used2 Unused.
+ */
+static void
+last_insert_id(sqlite3_context *context, int not_used,
+           sqlite3_value **not_used2)
+{
+    UNUSED_PARAMETER2(not_used, not_used2);
+    sqlite3_result_int(context, current_session()->sql_last_insert_id);
+}
+
+/*
   * Implementation of the total_changes() SQL function.  The return 
value is
   * the same as the sqlite3_total_changes() API function.
   */
@@ -1847,6 +1864,7 @@ sqlite3RegisterBuiltinFunctions(void)
          FUNCTION(lower, 1, 0, 1, LowerICUFunc),
          FUNCTION(hex, 1, 0, 0, hexFunc),
          FUNCTION2(ifnull, 2, 0, 0, noopFunc, SQLITE_FUNC_COALESCE),
+        VFUNCTION(last_insert_id, 0, 0, 0, last_insert_id),
          VFUNCTION(random, 0, 0, 0, randomFunc),
          VFUNCTION(randomblob, 1, 0, 0, randomBlob),
          FUNCTION(nullif, 2, 0, 1, nullifFunc),
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 13cb61e..8b5b0df 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -739,9 +739,7 @@ sqlite3Insert(Parse * pParse,    /* Parser context */
              if (j < 0 || nColumn == 0
                  || (pColumn && j >= pColumn->nId)) {
                  if (i == pTab->iAutoIncPKey) {
-                    sqlite3VdbeAddOp2(v,
-                              OP_NextAutoincValue,
-                              pTab->def->id,
+                    sqlite3VdbeAddOp2(v, OP_Null, 0,
                                iRegStore);
                      continue;
                  }
@@ -824,7 +822,14 @@ sqlite3Insert(Parse * pParse,    /* Parser context */
                          iRegStore);
              }
          }
-
+        /*
+         * Replace NULL in PK with autoincrement by
+         * generated value
+         */
+        if (pTab->iAutoIncPKey >= 0) {
+            sqlite3VdbeAddOp2(v, OP_NextAutoincValue, pTab->def->id,
+                      regData + pTab->iAutoIncPKey);
+        }
          /* Generate code to check constraints and generate index keys
             and do the insertion.
           */
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index dc5146f..dceb8c0 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -600,6 +600,11 @@ int sqlite3VdbeExec(Vdbe *p)
      u64 start;                 /* CPU clock count at start of opcode */
  #endif
      struct session *user_session = current_session();
+    /*
+     * Field sql_last_insert_id of current session should be
+     * set no more than once for each query.
+     */
+    bool is_last_insert_id_set = false;
      /*** INSERT STACK UNION HERE ***/

      assert(p->magic==VDBE_MAGIC_RUN);  /* sqlite3_step() verifies this */
@@ -1147,6 +1152,10 @@ case OP_NextAutoincValue: {
      assert(pOp->p1 > 0);
      assert(pOp->p2 > 0);

+    pIn1 = &p->aMem[pOp->p2];
+    if ((pIn1->flags & MEM_Null) == 0)
+        break;
+
      struct space *space = space_by_id(pOp->p1);
      if (space == NULL) {
          rc = SQL_TARANTOOL_ERROR;
@@ -1164,6 +1173,13 @@ case OP_NextAutoincValue: {
      pOut->flags = MEM_Int;
      pOut->u.i = value;

+    /* Set sql_last_insert_id of current session. */
+    if (!is_last_insert_id_set) {
+        struct session *session = current_session();
+        session->sql_last_insert_id = value;
+        is_last_insert_id_set = true;
+    }
+
      break;
  }

@@ -3745,7 +3761,7 @@ case OP_FCopy: {     /* out2 */

      if ((pOp->p3 & OPFLAG_NOOP_IF_NULL) && (pIn1->flags & MEM_Null)) {
          pOut = &aMem[pOp->p2];
-        if (pOut->flags & MEM_Undefined) pOut->flags = MEM_Null;
+        if (pOut->flags & MEM_Int) pOut->flags = MEM_Null;
          /* Flag is set and register is NULL -> do nothing  */
      } else {
          assert(memIsValid(pIn1));
diff --git a/test/sql-tap/insert3.test.lua b/test/sql-tap/insert3.test.lua
index c0e9d95..8394cd9 100755
--- a/test/sql-tap/insert3.test.lua
+++ b/test/sql-tap/insert3.test.lua
@@ -1,6 +1,6 @@
  #!/usr/bin/env tarantool
  test = require("sqltester")
-test:plan(18)
+test:plan(20)

  --!./tcltestrunner.lua
  -- 2005 January 13
@@ -262,6 +262,31 @@ test:do_execsql_test(
          -- <insert3-4.1>
  })

+-- gh-2618 function last_insert_id
+test:execsql("CREATE TABLE t8(id INTEGER PRIMARY KEY AUTOINCREMENT, a 
INT);")
+test:do_execsql_test(
+    "insert3-5.1",
+    [[
+        INSERT INTO t8 VALUES (null, 11);
+        SELECT LAST_INSERT_ID();
+    ]], {
+        -- <insert3-5.1>
+        1
+        -- <insert3-5.1>
+})
+
+test:do_execsql_test(
+    "insert3-5.2",
+    [[
+        INSERT INTO t8 VALUES (null, 44), (null, 55), (null, 66);
+        SELECT LAST_INSERT_ID();
+    ]], {
+        -- <insert3-5.1>
+        2
+        -- <insert3-5.1>
+})
+
+
  test:drop_all_tables()
  ---------------------------------------------------------------------------
  -- While developing tests for a different feature (savepoint) the 
following
diff --git a/test/sql/errinj.result b/test/sql/errinj.result
index a0ba60f..9443137 100644
--- a/test/sql/errinj.result
+++ b/test/sql/errinj.result
@@ -73,7 +73,8 @@ while f1:status() ~= 'dead' do fiber.sleep(0) end
  ...
  insert_res
  ---
-- rowcount: 1
+- last_insert_id: 0
+  rowcount: 1
  ...
  select_res
  ---
diff --git a/test/sql/gh-2981-check-autoinc.result 
b/test/sql/gh-2981-check-autoinc.result
index b0f55e6..43bd42b 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,6 @@ box.sql.execute("DROP TABLE t2")
  box.sql.execute("DROP TABLE t3")
  ---
  ...
+box.sql.execute("DROP TABLE t4")
+---
+...
diff --git a/test/sql/gh-2981-check-autoinc.test.lua 
b/test/sql/gh-2981-check-autoinc.test.lua
index 98a5fb4..593ff3a 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,10 @@ 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")
diff --git a/test/sql/iproto.result b/test/sql/iproto.result
index af474bc..5fdf49b 100644
--- a/test/sql/iproto.result
+++ b/test/sql/iproto.result
@@ -69,19 +69,23 @@ type(ret.rows[1])
  -- Operation with rowcount result.
  cn:execute('insert into test values (10, 11, NULL)')
  ---
-- rowcount: 1
+- last_insert_id: 0
+  rowcount: 1
  ...
  cn:execute('delete from test where a = 5')
  ---
-- rowcount: 1
+- last_insert_id: 0
+  rowcount: 1
  ...
  cn:execute('insert into test values (11, 12, NULL), (12, 12, NULL), 
(13, 12, NULL)')
  ---
-- rowcount: 3
+- last_insert_id: 0
+  rowcount: 3
  ...
  cn:execute('delete from test where a = 12')
  ---
-- rowcount: 3
+- last_insert_id: 0
+  rowcount: 3
  ...
  -- SQL errors.
  cn:execute('insert into not_existing_table values ("kek")')
@@ -330,7 +334,8 @@ cn:execute('select :value', parameters)
  -- gh-2608 SQL iproto DDL
  cn:execute('create table test2(id primary key, a, b, c)')
  ---
-- rowcount: 1
+- last_insert_id: 0
+  rowcount: 1
  ...
  box.space.TEST2.name
  ---
@@ -338,7 +343,8 @@ box.space.TEST2.name
  ...
  cn:execute('insert into test2 values (1, 1, 1, 1)')
  ---
-- rowcount: 1
+- last_insert_id: 0
+  rowcount: 1
  ...
  cn:execute('select * from test2')
  ---
@@ -352,7 +358,8 @@ cn:execute('select * from test2')
  ...
  cn:execute('create index test2_a_b_index on test2(a, b)')
  ---
-- rowcount: 1
+- last_insert_id: 0
+  rowcount: 1
  ...
  #box.space.TEST2.index
  ---
@@ -360,7 +367,8 @@ cn:execute('create index test2_a_b_index on test2(a, 
b)')
  ...
  cn:execute('drop table test2')
  ---
-- rowcount: 1
+- last_insert_id: 0
+  rowcount: 1
  ...
  box.space.TEST2
  ---
@@ -370,100 +378,121 @@ box.space.TEST2
  -- Test CREATE [IF NOT EXISTS] TABLE.
  cn:execute('create table test3(id primary key, a, b)')
  ---
-- rowcount: 1
+- last_insert_id: 0
+  rowcount: 1
  ...
  -- Rowcount = 1, although two tuples were created:
  -- for _space and for _index.
  cn:execute('insert into test3 values (1, 1, 1), (2, 2, 2), (3, 3, 3)')
  ---
-- rowcount: 3
+- last_insert_id: 0
+  rowcount: 3
  ...
  cn:execute('create table if not exists test3(id primary key)')
  ---
-- rowcount: 0
+- last_insert_id: 0
+  rowcount: 0
  ...
  -- Test CREATE VIEW [IF NOT EXISTS] and
  --      DROP   VIEW [IF EXISTS].
  cn:execute('create view test3_view(id) as select id from test3')
  ---
-- rowcount: 1
+- last_insert_id: 0
+  rowcount: 1
  ...
  cn:execute('create view if not exists test3_view(id) as select id from 
test3')
  ---
-- rowcount: 0
+- last_insert_id: 0
+  rowcount: 0
  ...
  cn:execute('drop view test3_view')
  ---
-- rowcount: 1
+- last_insert_id: 0
+  rowcount: 1
  ...
  cn:execute('drop view if exists test3_view')
  ---
-- rowcount: 0
+- last_insert_id: 0
+  rowcount: 0
  ...
  -- Test CREATE INDEX [IF NOT EXISTS] and
  --      DROP   INDEX [IF EXISTS].
  cn:execute('create index test3_sec on test3(a, b)')
  ---
-- rowcount: 1
+- last_insert_id: 0
+  rowcount: 1
  ...
  cn:execute('create index if not exists test3_sec on test3(a, b)')
  ---
-- rowcount: 0
+- last_insert_id: 0
+  rowcount: 0
  ...
  cn:execute('drop index test3_sec on test3')
  ---
-- rowcount: 1
+- last_insert_id: 0
+  rowcount: 1
  ...
  cn:execute('drop index if exists test3_sec on test3')
  ---
-- rowcount: 0
+- last_insert_id: 0
+  rowcount: 0
  ...
  -- Test CREATE TRIGGER [IF NOT EXISTS] and
  --      DROP   TRIGGER [IF EXISTS].
  cn:execute('create trigger trig INSERT ON test3 BEGIN SELECT * FROM 
test3; END;')
  ---
-- rowcount: 1
+- last_insert_id: 0
+  rowcount: 1
  ...
  cn:execute('create trigger if not exists trig INSERT ON test3 BEGIN 
SELECT * FROM test3; END;')
  ---
-- rowcount: 0
+- last_insert_id: 0
+  rowcount: 0
  ...
  cn:execute('drop trigger trig')
  ---
-- rowcount: 1
+- last_insert_id: 0
+  rowcount: 1
  ...
  cn:execute('drop trigger if exists trig')
  ---
-- rowcount: 0
+- last_insert_id: 0
+  rowcount: 0
  ...
  -- Test DROP TABLE [IF EXISTS].
  -- Create more indexes, triggers and _truncate tuple.
  cn:execute('create index idx1 on test3(a)')
  ---
-- rowcount: 1
+- last_insert_id: 0
+  rowcount: 1
  ...
  cn:execute('create index idx2 on test3(b)')
  ---
-- rowcount: 1
+- last_insert_id: 0
+  rowcount: 1
  ...
  box.space.TEST3:truncate()
  ---
  ...
  cn:execute('create trigger trig INSERT ON test3 BEGIN SELECT * FROM 
test3; END;')
  ---
-- rowcount: 1
+- last_insert_id: 0
+  rowcount: 1
  ...
  cn:execute('insert into test3 values (1, 1, 1), (2, 2, 2), (3, 3, 3)')
  ---
-- rowcount: 3
+- last_insert_id: 0
+  rowcount: 3
  ...
  cn:execute('drop table test3')
  ---
-- rowcount: 1
+- last_insert_id: 0
+  rowcount: 1
  ...
  cn:execute('drop table if exists test3')
  ---
-- rowcount: 0
+- last_insert_id: 0
+  rowcount: 0
  ...
  --
  -- gh-2948: sql: remove unnecessary templates for binding
@@ -535,7 +564,8 @@ cn = remote.connect(box.cfg.listen)
  ...
  cn:execute('create table test (id integer primary key, a integer, b 
integer)')
  ---
-- rowcount: 1
+- last_insert_id: 0
+  rowcount: 1
  ...
  future1 = cn:execute('insert into test values (1, 1, 1)', nil, nil, 
{is_async = true})
  ---
@@ -548,7 +578,8 @@ future3 = cn:execute('insert into test values (2, 2, 
2), (3, 3, 3)', nil, nil, {
  ...
  future1:wait_result()
  ---
-- rowcount: 1
+- last_insert_id: 0
+  rowcount: 1
  ...
  future2:wait_result()
  ---
@@ -558,7 +589,8 @@ future2:wait_result()
  ...
  future3:wait_result()
  ---
-- rowcount: 2
+- last_insert_id: 0
+  rowcount: 2
  ...
  future4 = cn:execute('select * from test', nil, nil, {is_async = true})
  ---
@@ -580,6 +612,91 @@ cn:close()
  box.sql.execute('drop table test')
  ---
  ...
+-- gh-2618 Return generated columns after INSERT in IPROTO.
+-- Return first PK autogenerated in last INSERT/REPLACE in which
+-- new PK was genereted in current session.
+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)', nil, nil)
+---
+- last_insert_id: 0
+  rowcount: 1
+...
+cn:execute('insert into test values (null, 2)', nil, nil)
+---
+- last_insert_id: 2
+  rowcount: 1
+...
+cn:execute('insert into test values (null, 2), (null, 3)', nil, nil)
+---
+- last_insert_id: 3
+  rowcount: 2
+...
+cn:execute('insert into test values (17, 2)', nil, nil)
+---
+- last_insert_id: 3
+  rowcount: 1
+...
+cn:execute('update test set a = 11 where id == 1', nil, nil)
+---
+- last_insert_id: 3
+  rowcount: 1
+...
+cn:execute('update test set a = 12 where id == 2', nil, nil)
+---
+- last_insert_id: 3
+  rowcount: 1
+...
+cn:execute('update test set a = 13 where id == 3', nil, nil)
+---
+- last_insert_id: 3
+  rowcount: 1
+...
+cn:execute('replace into test values (4, 44), (null, 100)', nil, nil)
+---
+- last_insert_id: 18
+  rowcount: 2
+...
+cn:execute('insert into test values (null, 2), (null, 3)', nil, nil)
+---
+- last_insert_id: 19
+  rowcount: 2
+...
+cn:execute('select * from test', nil, nil)
+---
+- metadata:
+  - name: ID
+  - name: A
+  rows:
+  - [1, 11]
+  - [2, 12]
+  - [3, 13]
+  - [4, 44]
+  - [17, 2]
+  - [18, 100]
+  - [19, 2]
+  - [20, 3]
+...
+cn:execute('create table test2 (id integer primary key, a integer)')
+---
+- last_insert_id: 19
+  rowcount: 1
+...
+cn:execute('drop table test2')
+---
+- last_insert_id: 19
+  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..db1ade5 100644
--- a/test/sql/iproto.test.lua
+++ b/test/sql/iproto.test.lua
@@ -201,6 +201,26 @@ future4:wait_result()
  cn:close()
  box.sql.execute('drop table test')

+-- gh-2618 Return generated columns after INSERT in IPROTO.
+-- Return first PK autogenerated in last INSERT/REPLACE in which
+-- new PK was genereted in current session.
+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)', nil, nil)
+cn:execute('insert into test values (null, 2)', nil, nil)
+cn:execute('insert into test values (null, 2), (null, 3)', nil, nil)
+cn:execute('insert into test values (17, 2)', nil, nil)
+cn:execute('update test set a = 11 where id == 1', nil, nil)
+cn:execute('update test set a = 12 where id == 2', nil, nil)
+cn:execute('update test set a = 13 where id == 3', nil, nil)
+cn:execute('replace into test values (4, 44), (null, 100)', nil, nil)
+cn:execute('insert into test values (null, 2), (null, 3)', nil, nil)
+cn:execute('select * from test', nil, nil)
+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

  reply	other threads:[~2018-08-13 12:34 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-08-03 10:28 [tarantool-patches] " imeevma
2018-08-06 15:24 ` [tarantool-patches] " Vladislav Shpilevoy
2018-08-13 12:34   ` Imeev Mergen [this message]
2018-08-16 22:05     ` Vladislav Shpilevoy
2018-08-17 11:43       ` Imeev Mergen
2018-08-21 15:50         ` Vladislav Shpilevoy
2018-08-22 13:31           ` Imeev Mergen
2018-08-24 10:05             ` Vladislav Shpilevoy

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=dee3598b-468a-db5b-e19e-d01346018980@tarantool.org \
    --to=imeevma@tarantool.org \
    --cc=tarantool-patches@freelists.org \
    --cc=v.shpilevoy@tarantool.org \
    --subject='[tarantool-patches] Re: [PATCH v2 1/1] sql: return last_insert_id via IPROTO' \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox