[tarantool-patches] Re: [PATCH v1 1/1] sql: cleanup on failed table creation

Vladislav Shpilevoy v.shpilevoy at tarantool.org
Mon Aug 13 18:10:55 MSK 2018


Also such error checking way can be useful to make this
https://github.com/tarantool/tarantool/issues/3435 more
correct. The way of 'if not exists' checking introduced
in 3435 was not applicable for index/table creation
exactly because of lack of destructors.

On 13/08/2018 17:30, Vladislav Shpilevoy wrote:
> Hi! Thanks for the patch! See 2 comments below.
> 
> On 09/08/2018 16:48, imeevma at tarantool.org wrote:
>> In some cases operation "create table" left some
>> created objects not deleted after operation fails.
>> After this patch all created objects will be deleted.
>>
>> Closes #3592.
>> ---
>> Branch: imeevma/gh-3592-cleanup-on-failed-create-table
> 
> 1. Please, put here a full link to the branch. Including
> https://github etc.
> 
>> Issue: https://github.com/tarantool/tarantool/issues/3592
>>
>>   src/box/sql/vdbe.c           | 54 ++++++++++++++++++++++++++++++++++++++++++++
>>   test/sql/drop-table.result   | 37 ++++++++++++++++++++++++++++++
>>   test/sql/drop-table.test.lua | 15 ++++++++++++
>>   3 files changed, 106 insertions(+)
>>
>> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
>> index ca89908..0583587 100644
>> --- a/src/box/sql/vdbe.c
>> +++ b/src/box/sql/vdbe.c
>> @@ -568,6 +568,23 @@ out2Prerelease(Vdbe *p, VdbeOp *pOp)
>>   }
>>   /*
>> + * Inserted in current query record.
>> + */
>> +struct inserted_record
> 
> 2. Have you tried to avoid introducing per-VdbeExec structure for this?
> I do not like list of such structures since it contradicts with Vdbe
> concept. For error checking we should generate error processing code
> instead of saving history somewhere. Now we have two programs actually:
> 
>      List of inserted records to rollback and Vdbe. There should be Vdbe
>      only, I believe.
> 
> Also, we allocate these records on each SInsert even if it is
> successful just to maybe rollback it, that is very unlikely case.
> 
> I propose to generate error processing code as Vdbe opcode. The
> program have labels on which the context makes goto on error.
> 
> For this we can add 2 arguments to OP_SInsert:
> label on which goto on error and label to goto on success and
> use it like this:
> 
> -- Table creation. --
> 
> 1. op_sinsert;
> if insert into _space then goto 4;
> else goto 3;
> 
> 2. op_sdelete;        -- This code called by next opcodes
> delete from _space;   -- on error via goto 2. It is 'destructor'
>                        -- of space creation.
> 
> 3. op_halt;
> 
> -- Index creation. --
> 
> 4. op_sinsert;
> if insert into _index then goto 7;
> else goto 2;
> 
> 5. op_sdelete;        -- This code is called by next opcodes
> delete from _index;   -- on error via goto 5. This code destructs
>                        -- the index and then fallback to the space
>                        -- destruction via goto 6 below.
> 
> 6. op_goto;
> goto 2;
> 
> -- Secondary index creation. --
> 
> 7. op_sinsert;
> if insert into _index then goto 10;
> else goto 5;
> 
> 8. op_sdelete         -- All the same. Next code does goto 8
> delete from _index;   -- on error.
> 
> 9. op_goto;
> goto 5;
> 
> ...
> 
> -- Next code should goto error_label on error.
> 
> -- i-th index creation
> 
> N. op_sinsert;
> if insert into _index then goto N + 3;
> else goto error_label;
> 
> N + 1. op_sdelete
> delete from _index;
> 
> N + 2. op_goto;
> goto error_label;
> 
> ...
> 
> This may look terrible and as a big patch, but it will not
> as I understand. It would be relatively easy patch. You can
> generate op_sdelete by the key you already have for op_sinsert.
> OP_Goto is already implemented. Goto from within of op_sinsert
> can be done via something like 'goto jump_to_p2'.
> 
> 
>> +{
>> +    /* Reference to previous record */
>> +    struct inserted_record *prev;> +    /* Space in which record was inserted */
>> +    struct space *space;
>> +    /* Length of packed primary key of record */
>> +    uint32_t len;
>> +    /* Flag to decrease nChanges if needed */
>> +    bool is_changes_increased;
>> +    /* Packed primary key of record */
>> +    char key[0];
>> +};
>> +
>> +/*
>>    * Execute as much of a VDBE program as we can.
>>    * This is the core of sqlite3_step().
>>    */
>> @@ -599,6 +616,7 @@ int sqlite3VdbeExec(Vdbe *p)
>>       u64 start;                 /* CPU clock count at start of opcode */
>>   #endif
>>       struct session *user_session = current_session();
>> +    struct inserted_record *inserted_record = NULL;
>>       /*** INSERT STACK UNION HERE ***/
>>       assert(p->magic==VDBE_MAGIC_RUN);  /* sqlite3_step() verifies this */
>> @@ -4352,6 +4370,28 @@ case OP_SInsert: {
>>           goto abort_due_to_error;
>>       if (pOp->p5 & OPFLAG_NCHANGE)
>>           p->nChange++;
>> +
>> +    uint32_t key_size;
>> +    char *key = tuple_extract_key_raw(pIn2->z, pIn2->z + pIn2->n,
>> +                      space->index[0]->def->key_def,
>> +                      &key_size);
>> +    if (key == NULL) {
>> +        rc = SQLITE_NOMEM_BKPT;
>> +        goto abort_due_to_error;
>> +    }
>> +    struct inserted_record *tmp =
>> +        (struct inserted_record *)malloc(sizeof(*tmp) + key_size);
>> +    if (tmp == NULL) {
>> +        rc = SQLITE_NOMEM_BKPT;
>> +        goto abort_due_to_error;
>> +    }
>> +    tmp->prev = inserted_record;
>> +    tmp->space = space;
>> +    tmp->len = key_size;
>> +    tmp->is_changes_increased = pOp->p5 & OPFLAG_NCHANGE;
>> +    memcpy(tmp->key, key, key_size);
>> +
>> +    inserted_record = tmp;
>>       break;
>>   }
>> @@ -5344,6 +5384,15 @@ default: {          /* This is really OP_Noop and OP_Explain */
>>        * an error of some kind.
>>        */
>>   abort_due_to_error:
>> +    while (inserted_record != NULL) {
>> +        sql_delete_by_key(inserted_record->space, inserted_record->key,
>> +                  inserted_record->len);
>> +        if (inserted_record->is_changes_increased)
>> +            p->nChange--;
>> +        struct inserted_record *tmp = inserted_record->prev;
>> +        free(inserted_record);
>> +        inserted_record = tmp;
>> +    }
>>       if (db->mallocFailed) rc = SQLITE_NOMEM_BKPT;
>>       assert(rc);
>>       if (p->zErrMsg==0 && rc!=SQLITE_IOERR_NOMEM) {
>> @@ -5372,6 +5421,11 @@ abort_due_to_error:
>>       /* This is the only way out of this procedure. */
>>   vdbe_return:
>> +    while (inserted_record != NULL) {
>> +        struct inserted_record *tmp = inserted_record->prev;
>> +        free(inserted_record);
>> +        inserted_record = tmp;
>> +    }
>>       testcase( nVmStep>0);
>>       p->aCounter[SQLITE_STMTSTATUS_VM_STEP] += (int)nVmStep;
>>       assert(rc!=SQLITE_OK || nExtraDelete==0
>> diff --git a/test/sql/drop-table.result b/test/sql/drop-table.result
>> index 08f2496..f1fdc17 100644
>> --- a/test/sql/drop-table.result
>> +++ b/test/sql/drop-table.result
>> @@ -33,3 +33,40 @@ box.sql.execute("INSERT INTO zzzoobar VALUES (111, 222, 'c3', 444)")
>>   -- DROP TABLE should do the job
>>   -- Debug
>>   -- require("console").start()
>> +-- gh-3592: net.box segmentation fault after "create table" with
>> +-- autoincrement
>> +test_run = require('test_run').new()
>> +---
>> +...
>> +box.schema.user.create('tmp')
>> +---
>> +...
>> +box.schema.user.grant('tmp', 'create', 'space')
>> +---
>> +...
>> +box.schema.user.grant('tmp', 'write', 'space', '_space')
>> +---
>> +...
>> +box.schema.user.grant('tmp', 'write', 'space', '_schema')
>> +---
>> +...
>> +box.session.su('tmp')
>> +---
>> +...
>> +-- Error - nothing should be created
>> +box.sql.execute('create table t (id integer primary key, a integer)')
>> +---
>> +- error: Write access to space '_index' is denied for user 'tmp'
>> +...
>> +box.space.T
>> +---
>> +- null
>> +...
>> +box.sql.execute('drop table t')
>> +---
>> +- error: 'no such table: T'
>> +...
>> +test_run:cmd('restart server default');
>> +box.schema.user.drop('tmp')
>> +---
>> +...
>> diff --git a/test/sql/drop-table.test.lua b/test/sql/drop-table.test.lua
>> index 9663074..f059619 100644
>> --- a/test/sql/drop-table.test.lua
>> +++ b/test/sql/drop-table.test.lua
>> @@ -25,3 +25,18 @@ box.sql.execute("INSERT INTO zzzoobar VALUES (111, 222, 'c3', 444)")
>>   -- Debug
>>   -- require("console").start()
>> +
>> +-- gh-3592: net.box segmentation fault after "create table" with
>> +-- autoincrement
>> +test_run = require('test_run').new()
>> +box.schema.user.create('tmp')
>> +box.schema.user.grant('tmp', 'create', 'space')
>> +box.schema.user.grant('tmp', 'write', 'space', '_space')
>> +box.schema.user.grant('tmp', 'write', 'space', '_schema')
>> +box.session.su('tmp')
>> +-- Error - nothing should be created
>> +box.sql.execute('create table t (id integer primary key, a integer)')
>> +box.space.T
>> +box.sql.execute('drop table t')
>> +test_run:cmd('restart server default');
>> +box.schema.user.drop('tmp')
>>
> 




More information about the Tarantool-patches mailing list