[tarantool-patches] Re: [PATCH v2 1/1] sql: cleanup on failed creation operation

Imeev Mergen imeevma at tarantool.org
Wed Oct 10 19:27:55 MSK 2018


Hello! Thank you for review! New patch and diff between last two
patches below.


On 10/09/2018 05:15 PM, n.pettik wrote:
> Attach please next time diff between two versions,
> so as I can avoid to review whole patch again.
>
> Also, check Travis before you send patch:
>
> https://travis-ci.org/tarantool/tarantool/jobs/438801852
> https://travis-ci.org/tarantool/tarantool/jobs/438801839
>
> Build fails.
It is better now:
https://travis-ci.org/tarantool/tarantool/builds/439677737

Test box-tap/feedback_daemon.test.lua failed but
there is ticket:
https://github.com/tarantool/tarantool/issues/3558

>>>>   void
>>>>   sql_finish_coding(struct Parse *parse_context)
>>>> @@ -62,6 +110,29 @@ sql_finish_coding(struct Parse *parse_context)
>>>>       struct sqlite3 *db = parse_context->db;
>>>>       struct Vdbe *v = sqlite3GetVdbe(parse_context);
>>>>       sqlite3VdbeAddOp0(v, OP_Halt);
>>>> +    /*
>>>> +     * Destructors for all created in current statement
>>>> +     * spaces, indexes, sequences etc. There is no need to
>>>> +     * create destructor for last SInsert.
>>>> +     */
>>>> +    if (rlist_empty(&parse_context->record_list) == 0) {
>>> Nit: if(! rlist_empty())
>> Fixed.
> No, it is not fixed.
Fixed.
>> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
>> index a806fb4..6a97ff9 100644
>> --- a/src/box/sql/build.c
>> +++ b/src/box/sql/build.c
>>   void
>>   sql_finish_coding(struct Parse *parse_context)
>>   {
>> @@ -62,6 +109,40 @@ sql_finish_coding(struct Parse *parse_context)
>>       struct sqlite3 *db = parse_context->db;
>>       struct Vdbe *v = sqlite3GetVdbe(parse_context);
>>       sqlite3VdbeAddOp0(v, OP_Halt);
>> +    /*
>> +     * In case statement "CREATE TABLE ..." fails it can
>> +     * left some records in system spaces that shouldn't be
>> +     * there. To clean-up properly this code is added. Last
>> +     * record isn't deleted because if statement fails than
>> +     * it won't be created. This code works the same way for
>> +     * other "CREATE ..." statements but it won't delete
>> +     * anything as these statements create no more than one
>> +     * record.
>> +     */
>> +    if (rlist_empty(&parse_context->record_list) == 0) {
>> +        struct saved_record *record =
>> +            rlist_shift_entry(&parse_context->record_list,
>> +                      struct saved_record, link);
>> +        /* Set P2 of SInsert. */
>> +        sqlite3VdbeChangeP2(v, record->insertion_opcode, v->nOp);
>> +        char *comment = "Delete entry from %s if CREATE TABLE fails”;
> Nit: const char *.
Fixed.
>> +        rlist_foreach_entry(record, &parse_context->record_list, link) {
>> +            int record_reg = ++parse_context->nMem;
>> +            sqlite3VdbeAddOp3(v, OP_MakeRecord, record->reg_key,
>> +                      record->reg_key_count, record_reg);
>> +            sqlite3VdbeAddOp2(v, OP_SDelete, record->space_id,
>> +                      record_reg);
>> +            struct space *space = space_by_id(record->space_id);
>> +            VdbeComment((v, comment, space_name(space)));
>> +            /* Set P2 of SInsert. */
>> +            sqlite3VdbeChangeP2(v, record->insertion_opcode,
>> +                        v->nOp);
>> +        }
>> +        sqlite3VdbeAddOp2(v, OP_Halt, SQL_TARANTOOL_ERROR, 0);
>> +        VdbeComment((v,
>> +                 "Exit with an error if CREATE statement fails"));
>> +    }
>> +
>>       if (db->mallocFailed || parse_context->nErr != 0) {
>>           if (parse_context->rc == SQLITE_OK)
>>               parse_context->rc = SQLITE_ERROR;
>> @@ -1101,13 +1182,14 @@ vdbe_emit_create_index(struct Parse *parse, struct space_def *def,
>>       sqlite3VdbeAddOp4(v, OP_Blob, index_parts_sz, entry_reg + 5,
>>                 SQL_SUBTYPE_MSGPACK, index_parts, P4_STATIC);
>>       sqlite3VdbeAddOp3(v, OP_MakeRecord, entry_reg, 6, tuple_reg);
>> -    sqlite3VdbeAddOp2(v, OP_SInsert, BOX_INDEX_ID, tuple_reg);
>> +    sqlite3VdbeAddOp3(v, OP_SInsert, BOX_INDEX_ID, 0, tuple_reg);
>>       /*
>>        * Non-NULL value means that index has been created via
>>        * separate CREATE INDEX statement.
>>        */
>>       if (idx_def->opts.sql != NULL)
>>           sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
>> +    save_record(parse, BOX_INDEX_ID, entry_reg, 2, v->nOp - 1);
> As I sad you don’t need to add to this list index entries when it comes
> to CREATE INDEX. You can tell index from unique constraint by
> existence of opts.sql string:
>
> +++ b/src/box/sql/build.c
> @@ -1185,11 +1185,15 @@ vdbe_emit_create_index(struct Parse *parse, struct space_def *def,
>          sqlite3VdbeAddOp3(v, OP_SInsert, BOX_INDEX_ID, 0, tuple_reg);
>          /*
>           * Non-NULL value means that index has been created via
> -        * separate CREATE INDEX statement.
> +        * separate CREATE INDEX statement. On the other hand,
> +        * we need to register all indexes incoming in
> +        * CREATE TABLE in order to remove them on table creation
> +        * fail.
>           */
>          if (idx_def->opts.sql != NULL)
>                  sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
> -       save_record(parse, BOX_INDEX_ID, entry_reg, 2, v->nOp - 1);
> +       else
> +               save_record(parse, BOX_INDEX_ID, entry_reg, 2, v->nOp - 1);
>          return;
>
> However, in this case we need to jump to abort_due_to_error label
> during processing of OP_SInsert. I don’t insist on this change tho,
> it is up to you.
Discussed and decided to left as it is.
>>       return;
>>   error:
>>       parse->rc = SQL_TARANTOOL_ERROR;
>> @@ -1165,8 +1247,9 @@ createSpace(Parse * pParse, int iSpaceId, char *zStmt)
>>       sqlite3VdbeAddOp4(v, OP_Blob, table_stmt_sz, iFirstCol + 6,
>>                 SQL_SUBTYPE_MSGPACK, table_stmt, P4_STATIC);
>>       sqlite3VdbeAddOp3(v, OP_MakeRecord, iFirstCol, 7, iRecord);
>> -    sqlite3VdbeAddOp2(v, OP_SInsert, BOX_SPACE_ID, iRecord);
>> +    sqlite3VdbeAddOp3(v, OP_SInsert, BOX_SPACE_ID, 0, iRecord);
>>       sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
>> +    save_record(pParse, BOX_SPACE_ID, iFirstCol, 1, v->nOp - 1);
>>       return;
>>   error:
>>       pParse->nErr++;
>> @@ -1340,9 +1423,11 @@ vdbe_emit_fkey_create(struct Parse *parse_context, const struct fkey_def *fk)
>>                 parent_links, P4_DYNAMIC);
>>       sqlite3VdbeAddOp3(vdbe, OP_MakeRecord, constr_tuple_reg, 9,
>>                 constr_tuple_reg + 9);
>> -    sqlite3VdbeAddOp2(vdbe, OP_SInsert, BOX_FK_CONSTRAINT_ID,
>> +    sqlite3VdbeAddOp3(vdbe, OP_SInsert, BOX_FK_CONSTRAINT_ID, 0,
>>                 constr_tuple_reg + 9);
>>       sqlite3VdbeChangeP5(vdbe, OPFLAG_NCHANGE);
>> +    save_record(parse_context, BOX_FK_CONSTRAINT_ID, constr_tuple_reg, 2,
>> +            vdbe->nOp - 1);
>>       sqlite3ReleaseTempRange(parse_context, constr_tuple_reg, 10);
>>       return;
>>   error:
>> @@ -1487,14 +1572,18 @@ sqlite3EndTable(Parse * pParse,    /* Parse context */
>>           int reg_seq_record =
>>               emitNewSysSequenceRecord(pParse, reg_seq_id,
>>                            p->def->name);
>> -        sqlite3VdbeAddOp2(v, OP_SInsert, BOX_SEQUENCE_ID,
>> +        sqlite3VdbeAddOp3(v, OP_SInsert, BOX_SEQUENCE_ID, 0,
>>                     reg_seq_record);
>> +        save_record(pParse, BOX_SEQUENCE_ID, reg_seq_record + 1, 1,
>> +                v->nOp - 1);
>>           /* Do an insertion into _space_sequence. */
>>           int reg_space_seq_record =
>>               emitNewSysSpaceSequenceRecord(pParse, reg_space_id,
>>                                 reg_seq_id);
>> -        sqlite3VdbeAddOp2(v, OP_SInsert, BOX_SPACE_SEQUENCE_ID,
>> +        sqlite3VdbeAddOp3(v, OP_SInsert, BOX_SPACE_SEQUENCE_ID, 0,
>>                     reg_space_seq_record);
>> +        save_record(pParse, BOX_SPACE_SEQUENCE_ID,
>> +                reg_space_seq_record + 1, 1, v->nOp - 1);
> If we are creating FK constraint with ALTER TABLE ADD CONSTRAINT,
> we don’t need to add it to list of things to be deleted (the same as for index).
>
> @@ -1426,8 +1430,10 @@ vdbe_emit_fkey_create(struct Parse *parse_context, const struct fkey_def *fk)
>          sqlite3VdbeAddOp3(vdbe, OP_SInsert, BOX_FK_CONSTRAINT_ID, 0,
>                            constr_tuple_reg + 9);
>          sqlite3VdbeChangeP5(vdbe, OPFLAG_NCHANGE);
> -       save_record(parse_context, BOX_FK_CONSTRAINT_ID, constr_tuple_reg, 2,
> -                   vdbe->nOp - 1);
> +       if (parse_context->pNewTable != NULL) {
> +               save_record(parse_context, BOX_FK_CONSTRAINT_ID,
> +                           constr_tuple_reg, 2, vdbe->nOp - 1);
> +       }
Discussed and decided to left as it is.
>> diff --git a/test/sql/drop-table.test.lua b/test/sql/drop-table.test.lua
>> index 9663074..1bc8894 100644
>> --- a/test/sql/drop-table.test.lua
>> +++ b/test/sql/drop-table.test.lua
>> @@ -25,3 +25,62 @@ box.sql.execute("INSERT INTO zzzoobar VALUES (111, 222, 'c3', 444)")
>> +
>> +box.session.su('admin')
>> +
>> +--
>> +-- Check that _space, _index and _sequence have the same number of
>> +-- records.
>> +--
>> +space_count == #box.space._space:select()
>> +index_count == #box.space._index:select()
>> +sequence_count == #box.space._sequence:select()
>> +
>> +--
>> +-- Give user right to write in _index. Still have not enough
>> +-- rights to write in _sequence.
>> +--
>> +box.schema.user.grant('tmp', 'write', 'space', '_index')
>> +box.session.su('tmp')
>> +
>> +--
>> +-- Error: user do not have rights to write in _sequence.
>> +--
>> +box.sql.execute('create table t2 (id int primary key autoincrement, a unique, b unique, c unique, d unique)’)
> Nit: for SQL statements please use uppercase.
Fixed.
>> +
>> +box.session.su('admin')
>> +
>> +--
>> +-- Check that _space, _index and _sequence have the same number of
>> +-- records.
>> +--
>> +space_count == #box.space._space:select()
>> +index_count == #box.space._index:select()
>> +sequence_count == #box.space._sequence:select()
>> +
>> +box.schema.user.drop('tmp’)
> I see no tests involving FK constraints. Add them as well.
>
Added.

*Diff between two last patches:*

diff --git a/6a97ff9..28b488c b/28b488c
index 6a97ff9..28b488c 100644
--- a/6a97ff9..28b488c
+++ b/28b488c
@@ -119,26 +119,28 @@ sql_finish_coding(struct Parse *parse_context)
       * anything as these statements create no more than one
       * record.
       */
-    if (rlist_empty(&parse_context->record_list) == 0) {
+    if (!rlist_empty(&parse_context->record_list)) {
          struct saved_record *record =
              rlist_shift_entry(&parse_context->record_list,
                        struct saved_record, link);
          /* Set P2 of SInsert. */
          sqlite3VdbeChangeP2(v, record->insertion_opcode, v->nOp);
-        char *comment = "Delete entry from %s if CREATE TABLE fails";
+        MAYBE_UNUSED const char *comment =
+            "Delete entry from %s if CREATE TABLE fails";
          rlist_foreach_entry(record, &parse_context->record_list, link) {
              int record_reg = ++parse_context->nMem;
              sqlite3VdbeAddOp3(v, OP_MakeRecord, record->reg_key,
                        record->reg_key_count, record_reg);
              sqlite3VdbeAddOp2(v, OP_SDelete, record->space_id,
                        record_reg);
-            struct space *space = space_by_id(record->space_id);
+            MAYBE_UNUSED struct space *space =
+                space_by_id(record->space_id);
              VdbeComment((v, comment, space_name(space)));
              /* Set P2 of SInsert. */
              sqlite3VdbeChangeP2(v, record->insertion_opcode,
                          v->nOp);
          }
-        sqlite3VdbeAddOp2(v, OP_Halt, SQL_TARANTOOL_ERROR, 0);
+        sqlite3VdbeAddOp1(v, OP_Halt, SQL_TARANTOOL_ERROR);
          VdbeComment((v,
                   "Exit with an error if CREATE statement fails"));
      }
diff --git a/test/sql/drop-table.result b/test/sql/drop-table.result
index 1659604..43da275 100644
--- a/test/sql/drop-table.result
+++ b/test/sql/drop-table.result
@@ -42,7 +42,7 @@ box.sql.execute("INSERT INTO zzzoobar VALUES (111, 
222, 'c3', 444)")
  box.schema.user.create('tmp')
  ---
  ...
-box.schema.user.grant('tmp', 'create', 'universe')
+box.schema.user.grant('tmp', 'create, read', 'universe')
  ---
  ...
  box.schema.user.grant('tmp', 'write', 'space', '_space')
@@ -68,12 +68,12 @@ box.session.su('tmp')
  -- Error: user do not have rights to write in box.space._index.
  -- Space that was already created should be automatically dropped.
  --
-box.sql.execute('create table t1 (id int primary key, a int)')
+box.sql.execute('CREATE TABLE t1 (id INT PRIMARY KEY, a INT)')
  ---
  - error: Write access to space '_index' is denied for user 'tmp'
  ...
  -- Error: no such table.
-box.sql.execute('drop table t1')
+box.sql.execute('DROP TABLE t1')
  ---
  - error: 'no such table: T1'
  ...
@@ -109,7 +109,7 @@ box.session.su('tmp')
  --
  -- Error: user do not have rights to write in _sequence.
  --
-box.sql.execute('create table t2 (id int primary key autoincrement, a 
unique, b unique, c unique, d unique)')
+box.sql.execute('CREATE TABLE t2 (id INT PRIMARY KEY AUTOINCREMENT, a 
UNIQUE, b UNIQUE, c UNIQUE, d UNIQUE)')
  ---
  - error: Write access to space '_sequence' is denied for user 'tmp'
  ...
@@ -132,6 +132,48 @@ sequence_count == #box.space._sequence:select()
  ---
  - true
  ...
+--
+-- Give user right to write in _sequence. Still have not enough
+-- rights to write in _fk_constraint.
+--
+box.schema.user.grant('tmp', 'write', 'space', '_sequence')
+---
+...
+box.session.su('tmp')
+---
+...
+box.sql.execute('CREATE TABLE t3(a INTEGER PRIMARY KEY);')
+---
+...
+--
+-- Error: user do not have rights to write in _fk_constraint.
+--
+box.sql.execute('CREATE TABLE t4(x INTEGER PRIMARY KEY REFERENCES t3);')
+---
+- error: Write access to space '_fk_constraint' is denied for user 'tmp'
+...
+box.sql.execute('DROP TABLE t3;')
+---
+...
+box.session.su('admin')
+---
+...
+--
+-- Check that _space, _index and _sequence have the same number of
+-- records.
+--
+space_count == #box.space._space:select()
+---
+- true
+...
+index_count == #box.space._index:select()
+---
+- true
+...
+sequence_count == #box.space._sequence:select()
+---
+- true
+...
  box.schema.user.drop('tmp')
  ---
  ...
diff --git a/test/sql/drop-table.test.lua b/test/sql/drop-table.test.lua
index 1bc8894..f86e3d8 100644
--- a/test/sql/drop-table.test.lua
+++ b/test/sql/drop-table.test.lua
@@ -33,7 +33,7 @@ box.sql.execute("INSERT INTO zzzoobar VALUES (111, 
222, 'c3', 444)")
  -- create index.
  --
  box.schema.user.create('tmp')
-box.schema.user.grant('tmp', 'create', 'universe')
+box.schema.user.grant('tmp', 'create, read', 'universe')
  box.schema.user.grant('tmp', 'write', 'space', '_space')
  box.schema.user.grant('tmp', 'write', 'space', '_schema')

@@ -47,9 +47,9 @@ box.session.su('tmp')
  -- Error: user do not have rights to write in box.space._index.
  -- Space that was already created should be automatically dropped.
  --
-box.sql.execute('create table t1 (id int primary key, a int)')
+box.sql.execute('CREATE TABLE t1 (id INT PRIMARY KEY, a INT)')
  -- Error: no such table.
-box.sql.execute('drop table t1')
+box.sql.execute('DROP TABLE t1')

  box.session.su('admin')

@@ -71,7 +71,31 @@ box.session.su('tmp')
  --
  -- Error: user do not have rights to write in _sequence.
  --
-box.sql.execute('create table t2 (id int primary key autoincrement, a 
unique, b unique, c unique, d unique)')
+box.sql.execute('CREATE TABLE t2 (id INT PRIMARY KEY AUTOINCREMENT, a 
UNIQUE, b UNIQUE, c UNIQUE, d UNIQUE)')
+
+box.session.su('admin')
+
+--
+-- Check that _space, _index and _sequence have the same number of
+-- records.
+--
+space_count == #box.space._space:select()
+index_count == #box.space._index:select()
+sequence_count == #box.space._sequence:select()
+
+--
+-- Give user right to write in _sequence. Still have not enough
+-- rights to write in _fk_constraint.
+--
+box.schema.user.grant('tmp', 'write', 'space', '_sequence')
+box.session.su('tmp')
+
+box.sql.execute('CREATE TABLE t3(a INTEGER PRIMARY KEY);')
+--
+-- Error: user do not have rights to write in _fk_constraint.
+--
+box.sql.execute('CREATE TABLE t4(x INTEGER PRIMARY KEY REFERENCES t3);')
+box.sql.execute('DROP TABLE t3;')

  box.session.su('admin')


*New patch:*

commit 876ac107e293bd184e247d0e8e299765cb986f9f
Author: Mergen Imeev <imeevma at gmail.com>
Date:   Fri Aug 31 15:50:17 2018 +0300

     sql: clean-up on failed CREATE TABLE

     In case statement "CREATE TABLE ..." fails it can left some
     records in system spaces that shouldn't be there. These records
     won't be left behind after this patch.

     @TarantoolBot document
     Title: Clean up after failure of CREATE TABLE
     Usually CREATE TABLE creates no less than two objects which are
     space and index. If creation of index (or any object after space)
     failed, created space (and other created objects) won't be deleted
     though operation failed. Now these objects will be deleted
     properly.

     Closes #3592

diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index a806fb4..28b488c 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -55,6 +55,53 @@
  #include "box/tuple_format.h"
  #include "box/coll_id_cache.h"

+/**
+ * Structure that contains information about record that was
+ * inserted into system space.
+ */
+struct saved_record
+{
+    /** A link in a record list. */
+    struct rlist link;
+    /** Id of space in which the record was inserted. */
+    uint32_t space_id;
+    /** First register of the key of the record. */
+    int reg_key;
+    /** Number of registers the key consists of. */
+    int reg_key_count;
+    /** The number of the OP_SInsert operation. */
+    int insertion_opcode;
+};
+
+/**
+ * Save inserted in system space record in list.
+ *
+ * @param parser SQL Parser object.
+ * @param space_id Id of table in which record is inserted.
+ * @param reg_key Register that contains first field of the key.
+ * @param reg_key_count Exact number of fields of the key.
+ * @param insertion_opcode Number of OP_SInsert opcode.
+ */
+static inline void
+save_record(struct Parse *parser, uint32_t space_id, int reg_key,
+        int reg_key_count, int insertion_opcode)
+{
+    struct saved_record *record =
+        region_alloc(&parser->region, sizeof(*record));
+    if (record == NULL) {
+        diag_set(OutOfMemory, sizeof(*record), "region_alloc",
+             "record");
+        parser->rc = SQL_TARANTOOL_ERROR;
+        parser->nErr++;
+        return;
+    }
+    record->space_id = space_id;
+    record->reg_key = reg_key;
+    record->reg_key_count = reg_key_count;
+    record->insertion_opcode = insertion_opcode;
+    rlist_add_entry(&parser->record_list, record, link);
+}
+
  void
  sql_finish_coding(struct Parse *parse_context)
  {
@@ -62,6 +109,42 @@ sql_finish_coding(struct Parse *parse_context)
      struct sqlite3 *db = parse_context->db;
      struct Vdbe *v = sqlite3GetVdbe(parse_context);
      sqlite3VdbeAddOp0(v, OP_Halt);
+    /*
+     * In case statement "CREATE TABLE ..." fails it can
+     * left some records in system spaces that shouldn't be
+     * there. To clean-up properly this code is added. Last
+     * record isn't deleted because if statement fails than
+     * it won't be created. This code works the same way for
+     * other "CREATE ..." statements but it won't delete
+     * anything as these statements create no more than one
+     * record.
+     */
+    if (!rlist_empty(&parse_context->record_list)) {
+        struct saved_record *record =
+            rlist_shift_entry(&parse_context->record_list,
+                      struct saved_record, link);
+        /* Set P2 of SInsert. */
+        sqlite3VdbeChangeP2(v, record->insertion_opcode, v->nOp);
+        MAYBE_UNUSED const char *comment =
+            "Delete entry from %s if CREATE TABLE fails";
+        rlist_foreach_entry(record, &parse_context->record_list, link) {
+            int record_reg = ++parse_context->nMem;
+            sqlite3VdbeAddOp3(v, OP_MakeRecord, record->reg_key,
+                      record->reg_key_count, record_reg);
+            sqlite3VdbeAddOp2(v, OP_SDelete, record->space_id,
+                      record_reg);
+            MAYBE_UNUSED struct space *space =
+                space_by_id(record->space_id);
+            VdbeComment((v, comment, space_name(space)));
+            /* Set P2 of SInsert. */
+            sqlite3VdbeChangeP2(v, record->insertion_opcode,
+                        v->nOp);
+        }
+        sqlite3VdbeAddOp1(v, OP_Halt, SQL_TARANTOOL_ERROR);
+        VdbeComment((v,
+                 "Exit with an error if CREATE statement fails"));
+    }
+
      if (db->mallocFailed || parse_context->nErr != 0) {
          if (parse_context->rc == SQLITE_OK)
              parse_context->rc = SQLITE_ERROR;
@@ -1101,13 +1184,14 @@ vdbe_emit_create_index(struct Parse *parse, 
struct space_def *def,
      sqlite3VdbeAddOp4(v, OP_Blob, index_parts_sz, entry_reg + 5,
                SQL_SUBTYPE_MSGPACK, index_parts, P4_STATIC);
      sqlite3VdbeAddOp3(v, OP_MakeRecord, entry_reg, 6, tuple_reg);
-    sqlite3VdbeAddOp2(v, OP_SInsert, BOX_INDEX_ID, tuple_reg);
+    sqlite3VdbeAddOp3(v, OP_SInsert, BOX_INDEX_ID, 0, tuple_reg);
      /*
       * Non-NULL value means that index has been created via
       * separate CREATE INDEX statement.
       */
      if (idx_def->opts.sql != NULL)
          sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
+    save_record(parse, BOX_INDEX_ID, entry_reg, 2, v->nOp - 1);
      return;
  error:
      parse->rc = SQL_TARANTOOL_ERROR;
@@ -1165,8 +1249,9 @@ createSpace(Parse * pParse, int iSpaceId, char *zStmt)
      sqlite3VdbeAddOp4(v, OP_Blob, table_stmt_sz, iFirstCol + 6,
                SQL_SUBTYPE_MSGPACK, table_stmt, P4_STATIC);
      sqlite3VdbeAddOp3(v, OP_MakeRecord, iFirstCol, 7, iRecord);
-    sqlite3VdbeAddOp2(v, OP_SInsert, BOX_SPACE_ID, iRecord);
+    sqlite3VdbeAddOp3(v, OP_SInsert, BOX_SPACE_ID, 0, iRecord);
      sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
+    save_record(pParse, BOX_SPACE_ID, iFirstCol, 1, v->nOp - 1);
      return;
  error:
      pParse->nErr++;
@@ -1340,9 +1425,11 @@ vdbe_emit_fkey_create(struct Parse 
*parse_context, const struct fkey_def *fk)
                parent_links, P4_DYNAMIC);
      sqlite3VdbeAddOp3(vdbe, OP_MakeRecord, constr_tuple_reg, 9,
                constr_tuple_reg + 9);
-    sqlite3VdbeAddOp2(vdbe, OP_SInsert, BOX_FK_CONSTRAINT_ID,
+    sqlite3VdbeAddOp3(vdbe, OP_SInsert, BOX_FK_CONSTRAINT_ID, 0,
                constr_tuple_reg + 9);
      sqlite3VdbeChangeP5(vdbe, OPFLAG_NCHANGE);
+    save_record(parse_context, BOX_FK_CONSTRAINT_ID, constr_tuple_reg, 2,
+            vdbe->nOp - 1);
      sqlite3ReleaseTempRange(parse_context, constr_tuple_reg, 10);
      return;
  error:
@@ -1487,14 +1574,18 @@ sqlite3EndTable(Parse * pParse,    /* Parse 
context */
          int reg_seq_record =
              emitNewSysSequenceRecord(pParse, reg_seq_id,
                           p->def->name);
-        sqlite3VdbeAddOp2(v, OP_SInsert, BOX_SEQUENCE_ID,
+        sqlite3VdbeAddOp3(v, OP_SInsert, BOX_SEQUENCE_ID, 0,
                    reg_seq_record);
+        save_record(pParse, BOX_SEQUENCE_ID, reg_seq_record + 1, 1,
+                v->nOp - 1);
          /* Do an insertion into _space_sequence. */
          int reg_space_seq_record =
              emitNewSysSpaceSequenceRecord(pParse, reg_space_id,
                                reg_seq_id);
-        sqlite3VdbeAddOp2(v, OP_SInsert, BOX_SPACE_SEQUENCE_ID,
+        sqlite3VdbeAddOp3(v, OP_SInsert, BOX_SPACE_SEQUENCE_ID, 0,
                    reg_space_seq_record);
+        save_record(pParse, BOX_SPACE_SEQUENCE_ID,
+                reg_space_seq_record + 1, 1, v->nOp - 1);
      }
      /* Code creation of FK constraints, if any. */
      struct fkey_parse *fk_parse;
diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
index e98e845..a4b65eb 100644
--- a/src/box/sql/prepare.c
+++ b/src/box/sql/prepare.c
@@ -274,6 +274,7 @@ sql_parser_create(struct Parse *parser, sqlite3 *db)
      memset(parser, 0, sizeof(struct Parse));
      parser->db = db;
      rlist_create(&parser->new_fkey);
+    rlist_create(&parser->record_list);
      region_create(&parser->region, &cord()->slabc);
  }

diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 744b660..d8eb516 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -2765,6 +2765,11 @@ struct Parse {
       * Foreign key constraint appeared in CREATE TABLE stmt.
       */
      struct rlist new_fkey;
+    /**
+     * List of all records that were inserted in system spaces
+     * in current statement.
+     */
+    struct rlist record_list;
      bool initiateTTrans;    /* Initiate Tarantool transaction */
      /** True, if table to be created has AUTOINCREMENT PK. */
      bool is_new_table_autoinc;
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 7c1015c..1bec2fa 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1010,8 +1010,12 @@ case OP_Halt: {
      p->pc = pcx;
      if (p->rc) {
          if (p->rc == SQL_TARANTOOL_ERROR) {
-            assert(pOp->p4.z != NULL);
-            box_error_set(__FILE__, __LINE__, pOp->p5, pOp->p4.z);
+            if (pOp->p4.z == NULL) {
+                assert(! diag_is_empty(diag_get()));
+            } else {
+                box_error_set(__FILE__, __LINE__, pOp->p5,
+                          pOp->p4.z);
+            }
          } else if (pOp->p5 != 0) {
              static const char * const azType[] = { "NOT NULL", 
"UNIQUE", "CHECK",
                                     "FOREIGN KEY" };
@@ -4308,8 +4312,8 @@ case OP_IdxInsert: {
      break;
  }

-/* Opcode: SInsert P1 P2 * * P5
- * Synopsis: space id = P1, key = r[P2]
+/* Opcode: SInsert P1 P2 P3 * P5
+ * Synopsis: space id = P1, key = r[P3], on error goto P2
   *
   * This opcode is used only during DDL routine.
   * In contrast to ordinary insertion, insertion to system spaces
@@ -4322,15 +4326,15 @@ case OP_IdxInsert: {
   */
  case OP_SInsert: {
      assert(pOp->p1 > 0);
-    assert(pOp->p2 >= 0);
+    assert(pOp->p2 > 0);
+    assert(pOp->p3 >= 0);

-    pIn2 = &aMem[pOp->p2];
+    pIn3 = &aMem[pOp->p3];
      struct space *space = space_by_id(pOp->p1);
      assert(space != NULL);
      assert(space_is_system(space));
-    rc = tarantoolSqlite3Insert(space, pIn2->z, pIn2->z + pIn2->n);
-    if (rc)
-        goto abort_due_to_error;
+    if (tarantoolSqlite3Insert(space, pIn3->z, pIn3->z + pIn3->n) != 0)
+        goto jump_to_p2;
      if (pOp->p5 & OPFLAG_NCHANGE)
          p->nChange++;
      break;
diff --git a/test/sql/drop-table.result b/test/sql/drop-table.result
index 08f2496..43da275 100644
--- a/test/sql/drop-table.result
+++ b/test/sql/drop-table.result
@@ -33,3 +33,147 @@ box.sql.execute("INSERT INTO zzzoobar VALUES (111, 
222, 'c3', 444)")
  -- DROP TABLE should do the job
  -- Debug
  -- require("console").start()
+--
+-- gh-3592: clean-up garbage on failed CREATE TABLE statement.
+--
+-- Let user have enough rights to create space, but not enough to
+-- create index.
+--
+box.schema.user.create('tmp')
+---
+...
+box.schema.user.grant('tmp', 'create, read', 'universe')
+---
+...
+box.schema.user.grant('tmp', 'write', 'space', '_space')
+---
+...
+box.schema.user.grant('tmp', 'write', 'space', '_schema')
+---
+...
+-- Number of records in _space, _index, _sequence:
+space_count = #box.space._space:select()
+---
+...
+index_count = #box.space._index:select()
+---
+...
+sequence_count = #box.space._sequence:select()
+---
+...
+box.session.su('tmp')
+---
+...
+--
+-- Error: user do not have rights to write in box.space._index.
+-- Space that was already created should be automatically dropped.
+--
+box.sql.execute('CREATE TABLE t1 (id INT PRIMARY KEY, a INT)')
+---
+- error: Write access to space '_index' is denied for user 'tmp'
+...
+-- Error: no such table.
+box.sql.execute('DROP TABLE t1')
+---
+- error: 'no such table: T1'
+...
+box.session.su('admin')
+---
+...
+--
+-- Check that _space, _index and _sequence have the same number of
+-- records.
+--
+space_count == #box.space._space:select()
+---
+- true
+...
+index_count == #box.space._index:select()
+---
+- true
+...
+sequence_count == #box.space._sequence:select()
+---
+- true
+...
+--
+-- Give user right to write in _index. Still have not enough
+-- rights to write in _sequence.
+--
+box.schema.user.grant('tmp', 'write', 'space', '_index')
+---
+...
+box.session.su('tmp')
+---
+...
+--
+-- Error: user do not have rights to write in _sequence.
+--
+box.sql.execute('CREATE TABLE t2 (id INT PRIMARY KEY AUTOINCREMENT, a 
UNIQUE, b UNIQUE, c UNIQUE, d UNIQUE)')
+---
+- error: Write access to space '_sequence' is denied for user 'tmp'
+...
+box.session.su('admin')
+---
+...
+--
+-- Check that _space, _index and _sequence have the same number of
+-- records.
+--
+space_count == #box.space._space:select()
+---
+- true
+...
+index_count == #box.space._index:select()
+---
+- true
+...
+sequence_count == #box.space._sequence:select()
+---
+- true
+...
+--
+-- Give user right to write in _sequence. Still have not enough
+-- rights to write in _fk_constraint.
+--
+box.schema.user.grant('tmp', 'write', 'space', '_sequence')
+---
+...
+box.session.su('tmp')
+---
+...
+box.sql.execute('CREATE TABLE t3(a INTEGER PRIMARY KEY);')
+---
+...
+--
+-- Error: user do not have rights to write in _fk_constraint.
+--
+box.sql.execute('CREATE TABLE t4(x INTEGER PRIMARY KEY REFERENCES t3);')
+---
+- error: Write access to space '_fk_constraint' is denied for user 'tmp'
+...
+box.sql.execute('DROP TABLE t3;')
+---
+...
+box.session.su('admin')
+---
+...
+--
+-- Check that _space, _index and _sequence have the same number of
+-- records.
+--
+space_count == #box.space._space:select()
+---
+- true
+...
+index_count == #box.space._index:select()
+---
+- true
+...
+sequence_count == #box.space._sequence:select()
+---
+- true
+...
+box.schema.user.drop('tmp')
+---
+...
diff --git a/test/sql/drop-table.test.lua b/test/sql/drop-table.test.lua
index 9663074..f86e3d8 100644
--- a/test/sql/drop-table.test.lua
+++ b/test/sql/drop-table.test.lua
@@ -25,3 +25,86 @@ box.sql.execute("INSERT INTO zzzoobar VALUES (111, 
222, 'c3', 444)")

  -- Debug
  -- require("console").start()
+
+--
+-- gh-3592: clean-up garbage on failed CREATE TABLE statement.
+--
+-- Let user have enough rights to create space, but not enough to
+-- create index.
+--
+box.schema.user.create('tmp')
+box.schema.user.grant('tmp', 'create, read', 'universe')
+box.schema.user.grant('tmp', 'write', 'space', '_space')
+box.schema.user.grant('tmp', 'write', 'space', '_schema')
+
+-- Number of records in _space, _index, _sequence:
+space_count = #box.space._space:select()
+index_count = #box.space._index:select()
+sequence_count = #box.space._sequence:select()
+
+box.session.su('tmp')
+--
+-- Error: user do not have rights to write in box.space._index.
+-- Space that was already created should be automatically dropped.
+--
+box.sql.execute('CREATE TABLE t1 (id INT PRIMARY KEY, a INT)')
+-- Error: no such table.
+box.sql.execute('DROP TABLE t1')
+
+box.session.su('admin')
+
+--
+-- Check that _space, _index and _sequence have the same number of
+-- records.
+--
+space_count == #box.space._space:select()
+index_count == #box.space._index:select()
+sequence_count == #box.space._sequence:select()
+
+--
+-- Give user right to write in _index. Still have not enough
+-- rights to write in _sequence.
+--
+box.schema.user.grant('tmp', 'write', 'space', '_index')
+box.session.su('tmp')
+
+--
+-- Error: user do not have rights to write in _sequence.
+--
+box.sql.execute('CREATE TABLE t2 (id INT PRIMARY KEY AUTOINCREMENT, a 
UNIQUE, b UNIQUE, c UNIQUE, d UNIQUE)')
+
+box.session.su('admin')
+
+--
+-- Check that _space, _index and _sequence have the same number of
+-- records.
+--
+space_count == #box.space._space:select()
+index_count == #box.space._index:select()
+sequence_count == #box.space._sequence:select()
+
+--
+-- Give user right to write in _sequence. Still have not enough
+-- rights to write in _fk_constraint.
+--
+box.schema.user.grant('tmp', 'write', 'space', '_sequence')
+box.session.su('tmp')
+
+box.sql.execute('CREATE TABLE t3(a INTEGER PRIMARY KEY);')
+--
+-- Error: user do not have rights to write in _fk_constraint.
+--
+box.sql.execute('CREATE TABLE t4(x INTEGER PRIMARY KEY REFERENCES t3);')
+box.sql.execute('DROP TABLE t3;')
+
+box.session.su('admin')
+
+--
+-- Check that _space, _index and _sequence have the same number of
+-- records.
+--
+space_count == #box.space._space:select()
+index_count == #box.space._index:select()
+sequence_count == #box.space._sequence:select()
+
+box.schema.user.drop('tmp')

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.tarantool.org/pipermail/tarantool-patches/attachments/20181010/541a0f24/attachment.html>


More information about the Tarantool-patches mailing list