From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Date: Mon, 9 Apr 2018 23:55:04 +0300 From: Konstantin Osipov Subject: Re: [PATCH 12/12] vinyl: allow to modify format of non-empty spaces Message-ID: <20180409205504.GM4527@atlas> References: MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: To: Vladimir Davydov Cc: tarantool-patches@freelists.org List-ID: * Vladimir Davydov [18/04/09 10:33]: > This patch implements space_vtab::check_space_format callback for vinyl > spaces. The callback iterates over all tuples stored in the primary > index and validates them against the new format. Since vinyl read > iterator may yield, this callback also installs an on_replace trigger > for the altered space. The trigger checks that tuples inserted during > ALTER conform to the new format and aborts ALTER if they do not. > > To test the feature, this patch enables memtx space format tests for > vinyl spaces by moving them from box/alter to engine/ddl. It adds just > one new vinyl-specific test case to vinyl/ddl. The test case checks > that tuples inserted during ALTER are validated against the new format. The patch is OK to push. Please consider doing all vinyl alter in vinyl worker threads, a major compaction is IMHO an OK way to achieve the same. Please check out one comment below. > --- > src/box/vinyl.c | 95 +++++- > test/box/alter.result | 810 +-------------------------------------------- > test/box/alter.test.lua | 319 ------------------ > test/engine/ddl.result | 787 ++++++++++++++++++++++++++++++++++++++++++- > test/engine/ddl.test.lua | 311 ++++++++++++++++- > test/vinyl/ddl.result | 83 ----- > test/vinyl/ddl.test.lua | 26 -- > test/vinyl/errinj.result | 75 +++++ > test/vinyl/errinj.test.lua | 35 ++ > 9 files changed, 1288 insertions(+), 1253 deletions(-) > > diff --git a/src/box/vinyl.c b/src/box/vinyl.c > index c99b518e..c2769e6d 100644 > --- a/src/box/vinyl.c > +++ b/src/box/vinyl.c > @@ -1028,21 +1028,98 @@ vinyl_space_prepare_alter(struct space *old_space, struct space *new_space) > return 0; > } > > +/** Argument passed to vy_check_format_on_replace(). */ > +struct vy_check_format_ctx { > + /** Format to check new tuples against. */ > + struct tuple_format *format; > + /** Set if a new tuple doesn't conform to the format. */ > + bool is_failed; > + /** Container for storing errors. */ > + struct diag diag; > +}; > + > +/** > + * This is an on_replace trigger callback that checks inserted > + * tuples against a new format. > + */ > +static void > +vy_check_format_on_replace(struct trigger *trigger, void *event) > +{ > + struct txn *txn = event; > + struct txn_stmt *stmt = txn_current_stmt(txn); > + struct vy_check_format_ctx *ctx = trigger->data; > + > + if (stmt->new_tuple == NULL) > + return; /* DELETE, nothing to do */ > + > + if (ctx->is_failed) > + return; /* already failed, nothing to do */ > + > + if (tuple_validate(ctx->format, stmt->new_tuple) != 0) { > + ctx->is_failed = true; > + diag_move(diag_get(), &ctx->diag); > + } > +} > + > static int > vinyl_space_check_format(struct space *space, struct tuple_format *format) > { > - (void)format; > struct vy_env *env = vy_env(space->engine); > - if (space->index_count == 0) > + > + /* > + * If this is local recovery, the space was checked before > + * restart so there's nothing we need to do. > + */ > + if (env->status == VINYL_INITIAL_RECOVERY_LOCAL || > + env->status == VINYL_FINAL_RECOVERY_LOCAL) > return 0; > + > + if (space->index_count == 0) > + return 0; /* space is empty, nothing to do */ > + > + /* > + * Iterate over all tuples stored in the given space and > + * check each of them for conformity to the new format. > + * Since read iterator may yield, we install an on_replace > + * trigger to check tuples inserted after we started the > + * iteration. > + */ > struct vy_lsm *pk = vy_lsm(space->index[0]); > - if (env->status != VINYL_ONLINE) > - return 0; > - if (pk->stat.disk.count.rows == 0 && pk->stat.memory.count.rows == 0) > - return 0; > - diag_set(ClientError, ER_UNSUPPORTED, "Vinyl", > - "changing format of a non-empty space"); > - return -1; > + > + struct tuple *key = vy_stmt_new_select(pk->env->key_format, NULL, 0); > + if (key == NULL) > + return -1; > + > + struct trigger on_replace; > + struct vy_check_format_ctx ctx; > + ctx.format = format; > + ctx.is_failed = false; > + diag_create(&ctx.diag); > + trigger_create(&on_replace, vy_check_format_on_replace, &ctx, NULL); > + trigger_add(&space->on_replace, &on_replace); > + > + struct vy_read_iterator itr; > + vy_read_iterator_open(&itr, pk, NULL, ITER_ALL, key, > + &env->xm->p_global_read_view); > + int rc; > + struct tuple *tuple; > + while ((rc = vy_read_iterator_next(&itr, &tuple)) == 0) { > + if (tuple == NULL) > + break; > + if (ctx.is_failed) { > + diag_move(&ctx.diag, diag_get()); > + rc = -1; > + break; > + } > + rc = tuple_validate(format, tuple); > + if (rc != 0) > + break; > + } Please don't assume this yields naturally - it won't in a whole bunch of critical cases. Better yet to move it to compaction thread and perform the check along with compaction. > + vy_read_iterator_close(&itr); > + diag_destroy(&ctx.diag); > + trigger_clear(&on_replace); > + tuple_unref(key); > + return rc; > } > > static void > diff --git a/test/box/alter.result b/test/box/alter.result > index 945b0cfd..a78dd3e9 100644 > --- a/test/box/alter.result > +++ b/test/box/alter.result > @@ -817,753 +817,6 @@ ts:drop() > --- > ... > -- > --- gh-2652: validate space format. > --- > -s = box.schema.space.create('test', { format = "format" }) > ---- > -- error: Illegal parameters, options parameter 'format' should be of type table > -... > -format = { { name = 100 } } > ---- > -... > -s = box.schema.space.create('test', { format = format }) > ---- > -- error: 'Illegal parameters, format[1]: name (string) is expected' > -... > -long = string.rep('a', box.schema.NAME_MAX + 1) > ---- > -... > -format = { { name = long } } > ---- > -... > -s = box.schema.space.create('test', { format = format }) > ---- > -- error: 'Failed to create space ''test'': field 1 name is too long' > -... > -format = { { name = 'id', type = '100' } } > ---- > -... > -s = box.schema.space.create('test', { format = format }) > ---- > -- error: 'Failed to create space ''test'': field 1 has unknown field type' > -... > -format = { utils.setmap({}) } > ---- > -... > -s = box.schema.space.create('test', { format = format }) > ---- > -- error: 'Illegal parameters, format[1]: name (string) is expected' > -... > --- Ensure the format is updated after index drop. > -format = { { name = 'id', type = 'unsigned' } } > ---- > -... > -s = box.schema.space.create('test', { format = format }) > ---- > -... > -pk = s:create_index('pk') > ---- > -... > -sk = s:create_index('sk', { parts = { 2, 'string' } }) > ---- > -... > -s:replace{1, 1} > ---- > -- error: 'Tuple field 2 type does not match one required by operation: expected string' > -... > -sk:drop() > ---- > -... > -s:replace{1, 1} > ---- > -- [1, 1] > -... > -s:drop() > ---- > -... > --- Check index parts conflicting with space format. > -format = { { name='field1', type='unsigned' }, { name='field2', type='string' }, { name='field3', type='scalar' } } > ---- > -... > -s = box.schema.space.create('test', { format = format }) > ---- > -... > -pk = s:create_index('pk') > ---- > -... > -sk1 = s:create_index('sk1', { parts = { 2, 'unsigned' } }) > ---- > -- error: Field 'field2' has type 'string' in space format, but type 'unsigned' in > - index definition > -... > --- Check space format conflicting with index parts. > -sk3 = s:create_index('sk3', { parts = { 2, 'string' } }) > ---- > -... > -format[2].type = 'unsigned' > ---- > -... > -s:format(format) > ---- > -- error: Field 'field2' has type 'unsigned' in space format, but type 'string' in > - index definition > -... > -s:format() > ---- > -- [{'name': 'field1', 'type': 'unsigned'}, {'name': 'field2', 'type': 'string'}, { > - 'name': 'field3', 'type': 'scalar'}] > -... > -s.index.sk3.parts > ---- > -- - type: string > - is_nullable: false > - fieldno: 2 > -... > --- Space format can be updated, if conflicted index is deleted. > -sk3:drop() > ---- > -... > -s:format(format) > ---- > -... > -s:format() > ---- > -- [{'name': 'field1', 'type': 'unsigned'}, {'name': 'field2', 'type': 'unsigned'}, > - {'name': 'field3', 'type': 'scalar'}] > -... > --- Check deprecated field types. > -format[2].type = 'num' > ---- > -... > -format[3].type = 'str' > ---- > -... > -format[4] = { name = 'field4', type = '*' } > ---- > -... > -format > ---- > -- - name: field1 > - type: unsigned > - - name: field2 > - type: num > - - name: field3 > - type: str > - - name: field4 > - type: '*' > -... > -s:format(format) > ---- > -... > -s:format() > ---- > -- [{'name': 'field1', 'type': 'unsigned'}, {'name': 'field2', 'type': 'num'}, {'name': 'field3', > - 'type': 'str'}, {'name': 'field4', 'type': '*'}] > -... > -s:replace{1, 2, '3', {4, 4, 4}} > ---- > -- [1, 2, '3', [4, 4, 4]] > -... > --- Check not indexed fields checking. > -s:truncate() > ---- > -... > -format[2] = {name='field2', type='string'} > ---- > -... > -format[3] = {name='field3', type='array'} > ---- > -... > -format[4] = {name='field4', type='number'} > ---- > -... > -format[5] = {name='field5', type='integer'} > ---- > -... > -format[6] = {name='field6', type='scalar'} > ---- > -... > -format[7] = {name='field7', type='map'} > ---- > -... > -format[8] = {name='field8', type='any'} > ---- > -... > -format[9] = {name='field9'} > ---- > -... > -s:format(format) > ---- > -... > --- Check incorrect field types. > -format[9] = {name='err', type='any'} > ---- > -... > -s:format(format) > ---- > -... > -s:replace{1, '2', {3, 3}, 4.4, -5, true, {value=7}, 8, 9} > ---- > -- [1, '2', [3, 3], 4.4, -5, true, {'value': 7}, 8, 9] > -... > -s:replace{1, 2, {3, 3}, 4.4, -5, true, {value=7}, 8, 9} > ---- > -- error: 'Tuple field 2 type does not match one required by operation: expected string' > -... > -s:replace{1, '2', 3, 4.4, -5, true, {value=7}, 8, 9} > ---- > -- error: 'Tuple field 3 type does not match one required by operation: expected array' > -... > -s:replace{1, '2', {3, 3}, '4', -5, true, {value=7}, 8, 9} > ---- > -- error: 'Tuple field 4 type does not match one required by operation: expected number' > -... > -s:replace{1, '2', {3, 3}, 4.4, -5.5, true, {value=7}, 8, 9} > ---- > -- error: 'Tuple field 5 type does not match one required by operation: expected integer' > -... > -s:replace{1, '2', {3, 3}, 4.4, -5, {6, 6}, {value=7}, 8, 9} > ---- > -- error: 'Tuple field 6 type does not match one required by operation: expected scalar' > -... > -s:replace{1, '2', {3, 3}, 4.4, -5, true, {7}, 8, 9} > ---- > -- error: 'Tuple field 7 type does not match one required by operation: expected map' > -... > -s:replace{1, '2', {3, 3}, 4.4, -5, true, {value=7}} > ---- > -- error: Tuple field count 7 is less than required by space format or defined indexes > - (expected at least 9) > -... > -s:replace{1, '2', {3, 3}, 4.4, -5, true, {value=7}, 8} > ---- > -- error: Tuple field count 8 is less than required by space format or defined indexes > - (expected at least 9) > -... > -s:truncate() > ---- > -... > --- > --- gh-1014: field names. > --- > -format = {} > ---- > -... > -format[1] = {name = 'field1', type = 'unsigned'} > ---- > -... > -format[2] = {name = 'field2'} > ---- > -... > -format[3] = {name = 'field1'} > ---- > -... > -s:format(format) > ---- > -- error: Space field 'field1' is duplicate > -... > -s:drop() > ---- > -... > --- https://github.com/tarantool/tarantool/issues/2815 > --- Extend space format definition syntax > -format = {{name='key',type='unsigned'}, {name='value',type='string'}} > ---- > -... > -s = box.schema.space.create('test', { format = format }) > ---- > -... > -s:format() > ---- > -- [{'name': 'key', 'type': 'unsigned'}, {'name': 'value', 'type': 'string'}] > -... > -s:format({'id', 'name'}) > ---- > -... > -s:format() > ---- > -- [{'name': 'id', 'type': 'any'}, {'name': 'name', 'type': 'any'}] > -... > -s:format({'id', {'name1'}}) > ---- > -... > -s:format() > ---- > -- [{'name': 'id', 'type': 'any'}, {'name': 'name1', 'type': 'any'}] > -... > -s:format({'id', {'name2', 'string'}}) > ---- > -... > -s:format() > ---- > -- [{'name': 'id', 'type': 'any'}, {'name': 'name2', 'type': 'string'}] > -... > -s:format({'id', {'name', type = 'string'}}) > ---- > -... > -s:format() > ---- > -- [{'name': 'id', 'type': 'any'}, {'name': 'name', 'type': 'string'}] > -... > -s:drop() > ---- > -... > -format = {'key', {'value',type='string'}} > ---- > -... > -s = box.schema.space.create('test', { format = format }) > ---- > -... > -s:format() > ---- > -- [{'name': 'key', 'type': 'any'}, {'name': 'value', 'type': 'string'}] > -... > -s:drop() > ---- > -... > -s = box.schema.space.create('test') > ---- > -... > -s:create_index('test', {parts = {'test'}}) > ---- > -- error: 'Illegal parameters, options.parts[1]: field was not found by name ''test''' > -... > -s:create_index('test', {parts = {{'test'}}}) > ---- > -- error: 'Illegal parameters, options.parts[1]: field was not found by name ''test''' > -... > -s:create_index('test', {parts = {{field = 'test'}}}) > ---- > -- error: 'Illegal parameters, options.parts[1]: field was not found by name ''test''' > -... > -s:create_index('test', {parts = {1}}).parts > ---- > -- - type: scalar > - is_nullable: false > - fieldno: 1 > -... > -s:drop() > ---- > -... > -s = box.schema.space.create('test') > ---- > -... > -s:format{{'test1', 'integer'}, 'test2', {'test3', 'integer'}, {'test4','scalar'}} > ---- > -... > -s:create_index('test', {parts = {'test'}}) > ---- > -- error: 'Illegal parameters, options.parts[1]: field was not found by name ''test''' > -... > -s:create_index('test', {parts = {{'test'}}}) > ---- > -- error: 'Illegal parameters, options.parts[1]: field was not found by name ''test''' > -... > -s:create_index('test', {parts = {{field = 'test'}}}) > ---- > -- error: 'Illegal parameters, options.parts[1]: field was not found by name ''test''' > -... > -s:create_index('test1', {parts = {'test1'}}).parts > ---- > -- - type: integer > - is_nullable: false > - fieldno: 1 > -... > -s:create_index('test2', {parts = {'test2'}}).parts > ---- > -- error: 'Can''t create or modify index ''test2'' in space ''test'': field type ''any'' > - is not supported' > -... > -s:create_index('test3', {parts = {{'test1', 'integer'}}}).parts > ---- > -- - type: integer > - is_nullable: false > - fieldno: 1 > -... > -s:create_index('test4', {parts = {{'test2', 'integer'}}}).parts > ---- > -- - type: integer > - is_nullable: false > - fieldno: 2 > -... > -s:create_index('test5', {parts = {{'test2', 'integer'}}}).parts > ---- > -- - type: integer > - is_nullable: false > - fieldno: 2 > -... > -s:create_index('test6', {parts = {1, 3}}).parts > ---- > -- - type: integer > - is_nullable: false > - fieldno: 1 > - - type: integer > - is_nullable: false > - fieldno: 3 > -... > -s:create_index('test7', {parts = {'test1', 4}}).parts > ---- > -- - type: integer > - is_nullable: false > - fieldno: 1 > - - type: scalar > - is_nullable: false > - fieldno: 4 > -... > -s:create_index('test8', {parts = {{1, 'integer'}, {'test4', 'scalar'}}}).parts > ---- > -- - type: integer > - is_nullable: false > - fieldno: 1 > - - type: scalar > - is_nullable: false > - fieldno: 4 > -... > -s:drop() > ---- > -... > --- > --- gh-2800: space formats checking is broken. > --- > --- Ensure that vinyl correctly process field count change. > -s = box.schema.space.create('test', {engine = 'vinyl', field_count = 2}) > ---- > -... > -pk = s:create_index('pk') > ---- > -... > -s:replace{1, 2} > ---- > -- [1, 2] > -... > -t = box.space._space:select{s.id}[1]:totable() > ---- > -... > -t[5] = 1 > ---- > -... > -box.space._space:replace(t) > ---- > -- error: Vinyl does not support changing format of a non-empty space > -... > -s:drop() > ---- > -... > --- Check field type changes. > -format = {} > ---- > -... > -format[1] = {name = 'field1', type = 'unsigned'} > ---- > -... > -format[2] = {name = 'field2', type = 'any'} > ---- > -... > -format[3] = {name = 'field3', type = 'unsigned'} > ---- > -... > -format[4] = {name = 'field4', type = 'string'} > ---- > -... > -format[5] = {name = 'field5', type = 'number'} > ---- > -... > -format[6] = {name = 'field6', type = 'integer'} > ---- > -... > -format[7] = {name = 'field7', type = 'boolean'} > ---- > -... > -format[8] = {name = 'field8', type = 'scalar'} > ---- > -... > -format[9] = {name = 'field9', type = 'array'} > ---- > -... > -format[10] = {name = 'field10', type = 'map'} > ---- > -... > -s = box.schema.space.create('test', {format = format}) > ---- > -... > -pk = s:create_index('pk') > ---- > -... > -t = s:replace{1, {2}, 3, '4', 5.5, -6, true, -8, {9, 9}, {val = 10}} > ---- > -... > -test_run:cmd("setopt delimiter ';'") > ---- > -- true > -... > -function fail_format_change(fieldno, new_type) > - local old_type = format[fieldno].type > - format[fieldno].type = new_type > - local ok, msg = pcall(s.format, s, format) > - format[fieldno].type = old_type > - return msg > -end; > ---- > -... > -function ok_format_change(fieldno, new_type) > - local old_type = format[fieldno].type > - format[fieldno].type = new_type > - s:format(format) > - s:delete{1} > - format[fieldno].type = old_type > - s:format(format) > - s:replace(t) > -end; > ---- > -... > -test_run:cmd("setopt delimiter ''"); > ---- > -- true > -... > --- any --X--> unsigned > -fail_format_change(2, 'unsigned') > ---- > -- 'Tuple field 2 type does not match one required by operation: expected unsigned' > -... > --- unsigned -----> any > -ok_format_change(3, 'any') > ---- > -... > --- unsigned --X--> string > -fail_format_change(3, 'string') > ---- > -- 'Tuple field 3 type does not match one required by operation: expected string' > -... > --- unsigned -----> number > -ok_format_change(3, 'number') > ---- > -... > --- unsigned -----> integer > -ok_format_change(3, 'integer') > ---- > -... > --- unsigned -----> scalar > -ok_format_change(3, 'scalar') > ---- > -... > --- unsigned --X--> map > -fail_format_change(3, 'map') > ---- > -- 'Tuple field 3 type does not match one required by operation: expected map' > -... > --- string -----> any > -ok_format_change(4, 'any') > ---- > -... > --- string -----> scalar > -ok_format_change(4, 'scalar') > ---- > -... > --- string --X--> boolean > -fail_format_change(4, 'boolean') > ---- > -- 'Tuple field 4 type does not match one required by operation: expected boolean' > -... > --- number -----> any > -ok_format_change(5, 'any') > ---- > -... > --- number -----> scalar > -ok_format_change(5, 'scalar') > ---- > -... > --- number --X--> integer > -fail_format_change(5, 'integer') > ---- > -- 'Tuple field 5 type does not match one required by operation: expected integer' > -... > --- integer -----> any > -ok_format_change(6, 'any') > ---- > -... > --- integer -----> number > -ok_format_change(6, 'number') > ---- > -... > --- integer -----> scalar > -ok_format_change(6, 'scalar') > ---- > -... > --- integer --X--> unsigned > -fail_format_change(6, 'unsigned') > ---- > -- 'Tuple field 6 type does not match one required by operation: expected unsigned' > -... > --- boolean -----> any > -ok_format_change(7, 'any') > ---- > -... > --- boolean -----> scalar > -ok_format_change(7, 'scalar') > ---- > -... > --- boolean --X--> string > -fail_format_change(7, 'string') > ---- > -- 'Tuple field 7 type does not match one required by operation: expected string' > -... > --- scalar -----> any > -ok_format_change(8, 'any') > ---- > -... > --- scalar --X--> unsigned > -fail_format_change(8, 'unsigned') > ---- > -- 'Tuple field 8 type does not match one required by operation: expected unsigned' > -... > --- array -----> any > -ok_format_change(9, 'any') > ---- > -... > --- array --X--> scalar > -fail_format_change(9, 'scalar') > ---- > -- 'Tuple field 9 type does not match one required by operation: expected scalar' > -... > --- map -----> any > -ok_format_change(10, 'any') > ---- > -... > --- map --X--> scalar > -fail_format_change(10, 'scalar') > ---- > -- 'Tuple field 10 type does not match one required by operation: expected scalar' > -... > -s:drop() > ---- > -... > --- Check new fields adding. > -format = {} > ---- > -... > -s = box.schema.space.create('test') > ---- > -... > -format[1] = {name = 'field1', type = 'unsigned'} > ---- > -... > -s:format(format) -- Ok, no indexes. > ---- > -... > -pk = s:create_index('pk') > ---- > -... > -format[2] = {name = 'field2', type = 'unsigned'} > ---- > -... > -s:format(format) -- Ok, empty space. > ---- > -... > -s:replace{1, 1} > ---- > -- [1, 1] > -... > -format[2] = nil > ---- > -... > -s:format(format) -- Ok, can delete fields with no checks. > ---- > -... > -s:delete{1} > ---- > -- [1, 1] > -... > -sk1 = s:create_index('sk1', {parts = {2, 'unsigned'}}) > ---- > -... > -sk2 = s:create_index('sk2', {parts = {3, 'unsigned'}}) > ---- > -... > -sk5 = s:create_index('sk5', {parts = {5, 'unsigned'}}) > ---- > -... > -s:replace{1, 1, 1, 1, 1} > ---- > -- [1, 1, 1, 1, 1] > -... > -format[2] = {name = 'field2', type = 'unsigned'} > ---- > -... > -format[3] = {name = 'field3', type = 'unsigned'} > ---- > -... > -format[4] = {name = 'field4', type = 'any'} > ---- > -... > -format[5] = {name = 'field5', type = 'unsigned'} > ---- > -... > --- Ok, all new fields are indexed or have type ANY, and new > --- field_count <= old field_count. > -s:format(format) > ---- > -... > -s:replace{1, 1, 1, 1, 1, 1} > ---- > -- [1, 1, 1, 1, 1, 1] > -... > -format[6] = {name = 'field6', type = 'unsigned'} > ---- > -... > --- Ok, but check existing tuples for a new field[6]. > -s:format(format) > ---- > -... > --- Fail, not enough fields. > -s:replace{2, 2, 2, 2, 2} > ---- > -- error: Tuple field count 5 is less than required by space format or defined indexes > - (expected at least 6) > -... > -s:replace{2, 2, 2, 2, 2, 2, 2} > ---- > -- [2, 2, 2, 2, 2, 2, 2] > -... > -format[7] = {name = 'field7', type = 'unsigned'} > ---- > -... > --- Fail, the tuple {1, ... 1} is invalid for a new format. > -s:format(format) > ---- > -- error: Tuple field count 6 is less than required by space format or defined indexes > - (expected at least 7) > -... > -s:drop() > ---- > -... > --- Vinyl does not support adding fields to a not empty space. > -s = box.schema.space.create('test', {engine = 'vinyl'}) > ---- > -... > -pk = s:create_index('pk') > ---- > -... > -s:replace{1,1} > ---- > -- [1, 1] > -... > -format = {} > ---- > -... > -format[1] = {name = 'field1', type = 'unsigned'} > ---- > -... > -format[2] = {name = 'field2', type = 'unsigned'} > ---- > -... > -s:format(format) > ---- > -- error: Vinyl does not support changing format of a non-empty space > -... > -s:drop() > ---- > -... > --- > -- gh-1557: NULL in indexes. > -- > NULL = require('msgpack').NULL > @@ -1607,7 +860,7 @@ s:create_index('primary', { parts = {'field1'} }) > is_nullable: false > fieldno: 1 > id: 0 > - space_id: 747 > + space_id: 733 > name: primary > type: TREE > ... > @@ -1684,7 +937,7 @@ s:create_index('secondary', { parts = {{2, 'string', is_nullable = true}} }) > is_nullable: true > fieldno: 2 > id: 1 > - space_id: 747 > + space_id: 733 > name: secondary > type: TREE > ... > @@ -1804,65 +1057,6 @@ s:drop() > --- > ... > -- > --- Allow to restrict space format, if corresponding restrictions > --- already are defined in indexes. > --- > -test_run:cmd("setopt delimiter ';'") > ---- > -- true > -... > -function check_format_restriction(engine, name) > - local s = box.schema.create_space(name, {engine = engine}) > - local pk = s:create_index('pk') > - local format = {} > - format[1] = {name = 'field1'} > - s:replace{1} > - s:replace{100} > - s:replace{0} > - s:format(format) > - s:format() > - format[1].type = 'unsigned' > - s:format(format) > -end; > ---- > -... > -test_run:cmd("setopt delimiter ''"); > ---- > -- true > -... > -check_format_restriction('memtx', 'test1') > ---- > -... > -check_format_restriction('vinyl', 'test2') > ---- > -... > -box.space.test1:format() > ---- > -- [{'name': 'field1', 'type': 'unsigned'}] > -... > -box.space.test1:select{} > ---- > -- - [0] > - - [1] > - - [100] > -... > -box.space.test2:format() > ---- > -- [{'name': 'field1', 'type': 'unsigned'}] > -... > -box.space.test2:select{} > ---- > -- - [0] > - - [1] > - - [100] > -... > -box.space.test1:drop() > ---- > -... > -box.space.test2:drop() > ---- > -... > --- > -- Allow to change is_nullable in index definition on non-empty > -- space. > -- > diff --git a/test/box/alter.test.lua b/test/box/alter.test.lua > index f6b2eb49..ab713584 100644 > --- a/test/box/alter.test.lua > +++ b/test/box/alter.test.lua > @@ -316,297 +316,6 @@ n > ts:drop() > > -- > --- gh-2652: validate space format. > --- > -s = box.schema.space.create('test', { format = "format" }) > -format = { { name = 100 } } > -s = box.schema.space.create('test', { format = format }) > -long = string.rep('a', box.schema.NAME_MAX + 1) > -format = { { name = long } } > -s = box.schema.space.create('test', { format = format }) > -format = { { name = 'id', type = '100' } } > -s = box.schema.space.create('test', { format = format }) > -format = { utils.setmap({}) } > -s = box.schema.space.create('test', { format = format }) > - > --- Ensure the format is updated after index drop. > -format = { { name = 'id', type = 'unsigned' } } > -s = box.schema.space.create('test', { format = format }) > -pk = s:create_index('pk') > -sk = s:create_index('sk', { parts = { 2, 'string' } }) > -s:replace{1, 1} > -sk:drop() > -s:replace{1, 1} > -s:drop() > - > --- Check index parts conflicting with space format. > -format = { { name='field1', type='unsigned' }, { name='field2', type='string' }, { name='field3', type='scalar' } } > -s = box.schema.space.create('test', { format = format }) > -pk = s:create_index('pk') > -sk1 = s:create_index('sk1', { parts = { 2, 'unsigned' } }) > - > --- Check space format conflicting with index parts. > -sk3 = s:create_index('sk3', { parts = { 2, 'string' } }) > -format[2].type = 'unsigned' > -s:format(format) > -s:format() > -s.index.sk3.parts > - > --- Space format can be updated, if conflicted index is deleted. > -sk3:drop() > -s:format(format) > -s:format() > - > --- Check deprecated field types. > -format[2].type = 'num' > -format[3].type = 'str' > -format[4] = { name = 'field4', type = '*' } > -format > -s:format(format) > -s:format() > -s:replace{1, 2, '3', {4, 4, 4}} > - > --- Check not indexed fields checking. > -s:truncate() > -format[2] = {name='field2', type='string'} > -format[3] = {name='field3', type='array'} > -format[4] = {name='field4', type='number'} > -format[5] = {name='field5', type='integer'} > -format[6] = {name='field6', type='scalar'} > -format[7] = {name='field7', type='map'} > -format[8] = {name='field8', type='any'} > -format[9] = {name='field9'} > -s:format(format) > - > --- Check incorrect field types. > -format[9] = {name='err', type='any'} > -s:format(format) > - > -s:replace{1, '2', {3, 3}, 4.4, -5, true, {value=7}, 8, 9} > -s:replace{1, 2, {3, 3}, 4.4, -5, true, {value=7}, 8, 9} > -s:replace{1, '2', 3, 4.4, -5, true, {value=7}, 8, 9} > -s:replace{1, '2', {3, 3}, '4', -5, true, {value=7}, 8, 9} > -s:replace{1, '2', {3, 3}, 4.4, -5.5, true, {value=7}, 8, 9} > -s:replace{1, '2', {3, 3}, 4.4, -5, {6, 6}, {value=7}, 8, 9} > -s:replace{1, '2', {3, 3}, 4.4, -5, true, {7}, 8, 9} > -s:replace{1, '2', {3, 3}, 4.4, -5, true, {value=7}} > -s:replace{1, '2', {3, 3}, 4.4, -5, true, {value=7}, 8} > -s:truncate() > - > --- > --- gh-1014: field names. > --- > -format = {} > -format[1] = {name = 'field1', type = 'unsigned'} > -format[2] = {name = 'field2'} > -format[3] = {name = 'field1'} > -s:format(format) > - > -s:drop() > - > --- https://github.com/tarantool/tarantool/issues/2815 > --- Extend space format definition syntax > -format = {{name='key',type='unsigned'}, {name='value',type='string'}} > -s = box.schema.space.create('test', { format = format }) > -s:format() > -s:format({'id', 'name'}) > -s:format() > -s:format({'id', {'name1'}}) > -s:format() > -s:format({'id', {'name2', 'string'}}) > -s:format() > -s:format({'id', {'name', type = 'string'}}) > -s:format() > -s:drop() > - > -format = {'key', {'value',type='string'}} > -s = box.schema.space.create('test', { format = format }) > -s:format() > -s:drop() > - > -s = box.schema.space.create('test') > -s:create_index('test', {parts = {'test'}}) > -s:create_index('test', {parts = {{'test'}}}) > -s:create_index('test', {parts = {{field = 'test'}}}) > -s:create_index('test', {parts = {1}}).parts > -s:drop() > - > -s = box.schema.space.create('test') > -s:format{{'test1', 'integer'}, 'test2', {'test3', 'integer'}, {'test4','scalar'}} > -s:create_index('test', {parts = {'test'}}) > -s:create_index('test', {parts = {{'test'}}}) > -s:create_index('test', {parts = {{field = 'test'}}}) > -s:create_index('test1', {parts = {'test1'}}).parts > -s:create_index('test2', {parts = {'test2'}}).parts > -s:create_index('test3', {parts = {{'test1', 'integer'}}}).parts > -s:create_index('test4', {parts = {{'test2', 'integer'}}}).parts > -s:create_index('test5', {parts = {{'test2', 'integer'}}}).parts > -s:create_index('test6', {parts = {1, 3}}).parts > -s:create_index('test7', {parts = {'test1', 4}}).parts > -s:create_index('test8', {parts = {{1, 'integer'}, {'test4', 'scalar'}}}).parts > -s:drop() > - > --- > --- gh-2800: space formats checking is broken. > --- > - > --- Ensure that vinyl correctly process field count change. > -s = box.schema.space.create('test', {engine = 'vinyl', field_count = 2}) > -pk = s:create_index('pk') > -s:replace{1, 2} > -t = box.space._space:select{s.id}[1]:totable() > -t[5] = 1 > -box.space._space:replace(t) > -s:drop() > - > --- Check field type changes. > -format = {} > -format[1] = {name = 'field1', type = 'unsigned'} > -format[2] = {name = 'field2', type = 'any'} > -format[3] = {name = 'field3', type = 'unsigned'} > -format[4] = {name = 'field4', type = 'string'} > -format[5] = {name = 'field5', type = 'number'} > -format[6] = {name = 'field6', type = 'integer'} > -format[7] = {name = 'field7', type = 'boolean'} > -format[8] = {name = 'field8', type = 'scalar'} > -format[9] = {name = 'field9', type = 'array'} > -format[10] = {name = 'field10', type = 'map'} > -s = box.schema.space.create('test', {format = format}) > -pk = s:create_index('pk') > -t = s:replace{1, {2}, 3, '4', 5.5, -6, true, -8, {9, 9}, {val = 10}} > - > -test_run:cmd("setopt delimiter ';'") > -function fail_format_change(fieldno, new_type) > - local old_type = format[fieldno].type > - format[fieldno].type = new_type > - local ok, msg = pcall(s.format, s, format) > - format[fieldno].type = old_type > - return msg > -end; > - > -function ok_format_change(fieldno, new_type) > - local old_type = format[fieldno].type > - format[fieldno].type = new_type > - s:format(format) > - s:delete{1} > - format[fieldno].type = old_type > - s:format(format) > - s:replace(t) > -end; > -test_run:cmd("setopt delimiter ''"); > - > --- any --X--> unsigned > -fail_format_change(2, 'unsigned') > - > --- unsigned -----> any > -ok_format_change(3, 'any') > --- unsigned --X--> string > -fail_format_change(3, 'string') > --- unsigned -----> number > -ok_format_change(3, 'number') > --- unsigned -----> integer > -ok_format_change(3, 'integer') > --- unsigned -----> scalar > -ok_format_change(3, 'scalar') > --- unsigned --X--> map > -fail_format_change(3, 'map') > - > --- string -----> any > -ok_format_change(4, 'any') > --- string -----> scalar > -ok_format_change(4, 'scalar') > --- string --X--> boolean > -fail_format_change(4, 'boolean') > - > --- number -----> any > -ok_format_change(5, 'any') > --- number -----> scalar > -ok_format_change(5, 'scalar') > --- number --X--> integer > -fail_format_change(5, 'integer') > - > --- integer -----> any > -ok_format_change(6, 'any') > --- integer -----> number > -ok_format_change(6, 'number') > --- integer -----> scalar > -ok_format_change(6, 'scalar') > --- integer --X--> unsigned > -fail_format_change(6, 'unsigned') > - > --- boolean -----> any > -ok_format_change(7, 'any') > --- boolean -----> scalar > -ok_format_change(7, 'scalar') > --- boolean --X--> string > -fail_format_change(7, 'string') > - > --- scalar -----> any > -ok_format_change(8, 'any') > --- scalar --X--> unsigned > -fail_format_change(8, 'unsigned') > - > --- array -----> any > -ok_format_change(9, 'any') > --- array --X--> scalar > -fail_format_change(9, 'scalar') > - > --- map -----> any > -ok_format_change(10, 'any') > --- map --X--> scalar > -fail_format_change(10, 'scalar') > - > -s:drop() > - > --- Check new fields adding. > -format = {} > -s = box.schema.space.create('test') > -format[1] = {name = 'field1', type = 'unsigned'} > -s:format(format) -- Ok, no indexes. > -pk = s:create_index('pk') > -format[2] = {name = 'field2', type = 'unsigned'} > -s:format(format) -- Ok, empty space. > -s:replace{1, 1} > -format[2] = nil > -s:format(format) -- Ok, can delete fields with no checks. > -s:delete{1} > -sk1 = s:create_index('sk1', {parts = {2, 'unsigned'}}) > -sk2 = s:create_index('sk2', {parts = {3, 'unsigned'}}) > -sk5 = s:create_index('sk5', {parts = {5, 'unsigned'}}) > -s:replace{1, 1, 1, 1, 1} > -format[2] = {name = 'field2', type = 'unsigned'} > -format[3] = {name = 'field3', type = 'unsigned'} > -format[4] = {name = 'field4', type = 'any'} > -format[5] = {name = 'field5', type = 'unsigned'} > --- Ok, all new fields are indexed or have type ANY, and new > --- field_count <= old field_count. > -s:format(format) > - > -s:replace{1, 1, 1, 1, 1, 1} > -format[6] = {name = 'field6', type = 'unsigned'} > --- Ok, but check existing tuples for a new field[6]. > -s:format(format) > - > --- Fail, not enough fields. > -s:replace{2, 2, 2, 2, 2} > - > -s:replace{2, 2, 2, 2, 2, 2, 2} > -format[7] = {name = 'field7', type = 'unsigned'} > --- Fail, the tuple {1, ... 1} is invalid for a new format. > -s:format(format) > -s:drop() > - > --- Vinyl does not support adding fields to a not empty space. > -s = box.schema.space.create('test', {engine = 'vinyl'}) > -pk = s:create_index('pk') > -s:replace{1,1} > -format = {} > -format[1] = {name = 'field1', type = 'unsigned'} > -format[2] = {name = 'field2', type = 'unsigned'} > -s:format(format) > -s:drop() > - > --- > -- gh-1557: NULL in indexes. > -- > > @@ -696,34 +405,6 @@ s:select{} > s:drop() > > -- > --- Allow to restrict space format, if corresponding restrictions > --- already are defined in indexes. > --- > -test_run:cmd("setopt delimiter ';'") > -function check_format_restriction(engine, name) > - local s = box.schema.create_space(name, {engine = engine}) > - local pk = s:create_index('pk') > - local format = {} > - format[1] = {name = 'field1'} > - s:replace{1} > - s:replace{100} > - s:replace{0} > - s:format(format) > - s:format() > - format[1].type = 'unsigned' > - s:format(format) > -end; > -test_run:cmd("setopt delimiter ''"); > -check_format_restriction('memtx', 'test1') > -check_format_restriction('vinyl', 'test2') > -box.space.test1:format() > -box.space.test1:select{} > -box.space.test2:format() > -box.space.test2:select{} > -box.space.test1:drop() > -box.space.test2:drop() > - > --- > -- Allow to change is_nullable in index definition on non-empty > -- space. > -- > diff --git a/test/engine/ddl.result b/test/engine/ddl.result > index 308aefb0..04062ac1 100644 > --- a/test/engine/ddl.result > +++ b/test/engine/ddl.result > @@ -357,7 +357,7 @@ space:drop() > format = {{'field1', 'scalar'}} > --- > ... > -s = box.schema.create_space('test', {format = format}) > +s = box.schema.space.create('test', {engine = engine, format = format}) > --- > ... > pk = s:create_index('pk') > @@ -374,7 +374,7 @@ s:drop() > format = {{'field1'}} > --- > ... > -s = box.schema.create_space('test', {format = format}) > +s = box.schema.space.create('test', {engine = engine, format = format}) > --- > ... > pk = s:create_index('pk') > @@ -391,7 +391,7 @@ s:drop() > -- gh-3229: update optionality if a space format is changed too, > -- not only when indexes are updated. > -- > -s = box.schema.create_space('test', {engine = engine}) > +s = box.schema.space.create('test', {engine = engine}) > --- > ... > format = {} > @@ -466,7 +466,7 @@ s:drop() > -- > -- Modify key definition without index rebuild. > -- > -s = box.schema.create_space('test', {engine = engine}) > +s = box.schema.space.create('test', {engine = engine}) > --- > ... > i1 = s:create_index('i1', {unique = true, parts = {1, 'unsigned'}}) > @@ -573,3 +573,782 @@ i3:select() > s:drop() > --- > ... > +-- > +-- gh-2652: validate space format. > +-- > +s = box.schema.space.create('test', { engine = engine, format = "format" }) > +--- > +- error: Illegal parameters, options parameter 'format' should be of type table > +... > +format = { { name = 100 } } > +--- > +... > +s = box.schema.space.create('test', { engine = engine, format = format }) > +--- > +- error: 'Illegal parameters, format[1]: name (string) is expected' > +... > +long = string.rep('a', box.schema.NAME_MAX + 1) > +--- > +... > +format = { { name = long } } > +--- > +... > +s = box.schema.space.create('test', { engine = engine, format = format }) > +--- > +- error: 'Failed to create space ''test'': field 1 name is too long' > +... > +format = { { name = 'id', type = '100' } } > +--- > +... > +s = box.schema.space.create('test', { engine = engine, format = format }) > +--- > +- error: 'Failed to create space ''test'': field 1 has unknown field type' > +... > +format = { setmetatable({}, { __serialize = 'map' }) } > +--- > +... > +s = box.schema.space.create('test', { engine = engine, format = format }) > +--- > +- error: 'Illegal parameters, format[1]: name (string) is expected' > +... > +-- Ensure the format is updated after index drop. > +format = { { name = 'id', type = 'unsigned' } } > +--- > +... > +s = box.schema.space.create('test', { engine = engine, format = format }) > +--- > +... > +pk = s:create_index('pk') > +--- > +... > +sk = s:create_index('sk', { parts = { 2, 'string' } }) > +--- > +... > +s:replace{1, 1} > +--- > +- error: 'Tuple field 2 type does not match one required by operation: expected string' > +... > +sk:drop() > +--- > +... > +s:replace{1, 1} > +--- > +- [1, 1] > +... > +s:drop() > +--- > +... > +-- Check index parts conflicting with space format. > +format = { { name='field1', type='unsigned' }, { name='field2', type='string' }, { name='field3', type='scalar' } } > +--- > +... > +s = box.schema.space.create('test', { engine = engine, format = format }) > +--- > +... > +pk = s:create_index('pk') > +--- > +... > +sk1 = s:create_index('sk1', { parts = { 2, 'unsigned' } }) > +--- > +- error: Field 'field2' has type 'string' in space format, but type 'unsigned' in > + index definition > +... > +-- Check space format conflicting with index parts. > +sk3 = s:create_index('sk3', { parts = { 2, 'string' } }) > +--- > +... > +format[2].type = 'unsigned' > +--- > +... > +s:format(format) > +--- > +- error: Field 'field2' has type 'unsigned' in space format, but type 'string' in > + index definition > +... > +s:format() > +--- > +- [{'name': 'field1', 'type': 'unsigned'}, {'name': 'field2', 'type': 'string'}, { > + 'name': 'field3', 'type': 'scalar'}] > +... > +s.index.sk3.parts > +--- > +- - type: string > + is_nullable: false > + fieldno: 2 > +... > +-- Space format can be updated, if conflicted index is deleted. > +sk3:drop() > +--- > +... > +s:format(format) > +--- > +... > +s:format() > +--- > +- [{'name': 'field1', 'type': 'unsigned'}, {'name': 'field2', 'type': 'unsigned'}, > + {'name': 'field3', 'type': 'scalar'}] > +... > +-- Check deprecated field types. > +format[2].type = 'num' > +--- > +... > +format[3].type = 'str' > +--- > +... > +format[4] = { name = 'field4', type = '*' } > +--- > +... > +format > +--- > +- - name: field1 > + type: unsigned > + - name: field2 > + type: num > + - name: field3 > + type: str > + - name: field4 > + type: '*' > +... > +s:format(format) > +--- > +... > +s:format() > +--- > +- [{'name': 'field1', 'type': 'unsigned'}, {'name': 'field2', 'type': 'num'}, {'name': 'field3', > + 'type': 'str'}, {'name': 'field4', 'type': '*'}] > +... > +s:replace{1, 2, '3', {4, 4, 4}} > +--- > +- [1, 2, '3', [4, 4, 4]] > +... > +-- Check not indexed fields checking. > +s:truncate() > +--- > +... > +format[2] = {name='field2', type='string'} > +--- > +... > +format[3] = {name='field3', type='array'} > +--- > +... > +format[4] = {name='field4', type='number'} > +--- > +... > +format[5] = {name='field5', type='integer'} > +--- > +... > +format[6] = {name='field6', type='scalar'} > +--- > +... > +format[7] = {name='field7', type='map'} > +--- > +... > +format[8] = {name='field8', type='any'} > +--- > +... > +format[9] = {name='field9'} > +--- > +... > +s:format(format) > +--- > +... > +-- Check incorrect field types. > +format[9] = {name='err', type='any'} > +--- > +... > +s:format(format) > +--- > +... > +s:replace{1, '2', {3, 3}, 4.4, -5, true, {value=7}, 8, 9} > +--- > +- [1, '2', [3, 3], 4.4, -5, true, {'value': 7}, 8, 9] > +... > +s:replace{1, 2, {3, 3}, 4.4, -5, true, {value=7}, 8, 9} > +--- > +- error: 'Tuple field 2 type does not match one required by operation: expected string' > +... > +s:replace{1, '2', 3, 4.4, -5, true, {value=7}, 8, 9} > +--- > +- error: 'Tuple field 3 type does not match one required by operation: expected array' > +... > +s:replace{1, '2', {3, 3}, '4', -5, true, {value=7}, 8, 9} > +--- > +- error: 'Tuple field 4 type does not match one required by operation: expected number' > +... > +s:replace{1, '2', {3, 3}, 4.4, -5.5, true, {value=7}, 8, 9} > +--- > +- error: 'Tuple field 5 type does not match one required by operation: expected integer' > +... > +s:replace{1, '2', {3, 3}, 4.4, -5, {6, 6}, {value=7}, 8, 9} > +--- > +- error: 'Tuple field 6 type does not match one required by operation: expected scalar' > +... > +s:replace{1, '2', {3, 3}, 4.4, -5, true, {7}, 8, 9} > +--- > +- error: 'Tuple field 7 type does not match one required by operation: expected map' > +... > +s:replace{1, '2', {3, 3}, 4.4, -5, true, {value=7}} > +--- > +- error: Tuple field count 7 is less than required by space format or defined indexes > + (expected at least 9) > +... > +s:replace{1, '2', {3, 3}, 4.4, -5, true, {value=7}, 8} > +--- > +- error: Tuple field count 8 is less than required by space format or defined indexes > + (expected at least 9) > +... > +s:truncate() > +--- > +... > +-- > +-- gh-1014: field names. > +-- > +format = {} > +--- > +... > +format[1] = {name = 'field1', type = 'unsigned'} > +--- > +... > +format[2] = {name = 'field2'} > +--- > +... > +format[3] = {name = 'field1'} > +--- > +... > +s:format(format) > +--- > +- error: Space field 'field1' is duplicate > +... > +s:drop() > +--- > +... > +-- https://github.com/tarantool/tarantool/issues/2815 > +-- Extend space format definition syntax > +format = {{name='key',type='unsigned'}, {name='value',type='string'}} > +--- > +... > +s = box.schema.space.create('test', { engine = engine, format = format }) > +--- > +... > +s:format() > +--- > +- [{'name': 'key', 'type': 'unsigned'}, {'name': 'value', 'type': 'string'}] > +... > +s:format({'id', 'name'}) > +--- > +... > +s:format() > +--- > +- [{'name': 'id', 'type': 'any'}, {'name': 'name', 'type': 'any'}] > +... > +s:format({'id', {'name1'}}) > +--- > +... > +s:format() > +--- > +- [{'name': 'id', 'type': 'any'}, {'name': 'name1', 'type': 'any'}] > +... > +s:format({'id', {'name2', 'string'}}) > +--- > +... > +s:format() > +--- > +- [{'name': 'id', 'type': 'any'}, {'name': 'name2', 'type': 'string'}] > +... > +s:format({'id', {'name', type = 'string'}}) > +--- > +... > +s:format() > +--- > +- [{'name': 'id', 'type': 'any'}, {'name': 'name', 'type': 'string'}] > +... > +s:drop() > +--- > +... > +format = {'key', {'value',type='string'}} > +--- > +... > +s = box.schema.space.create('test', { engine = engine, format = format }) > +--- > +... > +s:format() > +--- > +- [{'name': 'key', 'type': 'any'}, {'name': 'value', 'type': 'string'}] > +... > +s:drop() > +--- > +... > +s = box.schema.space.create('test', { engine = engine }) > +--- > +... > +s:create_index('test', {parts = {'test'}}) > +--- > +- error: 'Illegal parameters, options.parts[1]: field was not found by name ''test''' > +... > +s:create_index('test', {parts = {{'test'}}}) > +--- > +- error: 'Illegal parameters, options.parts[1]: field was not found by name ''test''' > +... > +s:create_index('test', {parts = {{field = 'test'}}}) > +--- > +- error: 'Illegal parameters, options.parts[1]: field was not found by name ''test''' > +... > +s:create_index('test', {parts = {1}}).parts > +--- > +- - type: scalar > + is_nullable: false > + fieldno: 1 > +... > +s:drop() > +--- > +... > +s = box.schema.space.create('test', { engine = engine }) > +--- > +... > +s:format{{'test1', 'integer'}, 'test2', {'test3', 'integer'}, {'test4','scalar'}} > +--- > +... > +s:create_index('test', {parts = {'test'}}) > +--- > +- error: 'Illegal parameters, options.parts[1]: field was not found by name ''test''' > +... > +s:create_index('test', {parts = {{'test'}}}) > +--- > +- error: 'Illegal parameters, options.parts[1]: field was not found by name ''test''' > +... > +s:create_index('test', {parts = {{field = 'test'}}}) > +--- > +- error: 'Illegal parameters, options.parts[1]: field was not found by name ''test''' > +... > +s:create_index('test1', {parts = {'test1'}}).parts > +--- > +- - type: integer > + is_nullable: false > + fieldno: 1 > +... > +s:create_index('test2', {parts = {'test2'}}).parts > +--- > +- error: 'Can''t create or modify index ''test2'' in space ''test'': field type ''any'' > + is not supported' > +... > +s:create_index('test3', {parts = {{'test1', 'integer'}}}).parts > +--- > +- - type: integer > + is_nullable: false > + fieldno: 1 > +... > +s:create_index('test4', {parts = {{'test2', 'integer'}}}).parts > +--- > +- - type: integer > + is_nullable: false > + fieldno: 2 > +... > +s:create_index('test5', {parts = {{'test2', 'integer'}}}).parts > +--- > +- - type: integer > + is_nullable: false > + fieldno: 2 > +... > +s:create_index('test6', {parts = {1, 3}}).parts > +--- > +- - type: integer > + is_nullable: false > + fieldno: 1 > + - type: integer > + is_nullable: false > + fieldno: 3 > +... > +s:create_index('test7', {parts = {'test1', 4}}).parts > +--- > +- - type: integer > + is_nullable: false > + fieldno: 1 > + - type: scalar > + is_nullable: false > + fieldno: 4 > +... > +s:create_index('test8', {parts = {{1, 'integer'}, {'test4', 'scalar'}}}).parts > +--- > +- - type: integer > + is_nullable: false > + fieldno: 1 > + - type: scalar > + is_nullable: false > + fieldno: 4 > +... > +s:drop() > +--- > +... > +-- > +-- gh-2800: space formats checking is broken. > +-- > +-- Ensure that vinyl correctly process field count change. > +s = box.schema.space.create('test', {engine = engine, field_count = 2}) > +--- > +... > +pk = s:create_index('pk') > +--- > +... > +s:replace{1, 2} > +--- > +- [1, 2] > +... > +t = box.space._space:select{s.id}[1]:totable() > +--- > +... > +t[5] = 1 > +--- > +... > +box.space._space:replace(t) > +--- > +- error: Tuple field count 2 does not match space field count 1 > +... > +s:drop() > +--- > +... > +-- Check field type changes. > +format = {} > +--- > +... > +format[1] = {name = 'field1', type = 'unsigned'} > +--- > +... > +format[2] = {name = 'field2', type = 'any'} > +--- > +... > +format[3] = {name = 'field3', type = 'unsigned'} > +--- > +... > +format[4] = {name = 'field4', type = 'string'} > +--- > +... > +format[5] = {name = 'field5', type = 'number'} > +--- > +... > +format[6] = {name = 'field6', type = 'integer'} > +--- > +... > +format[7] = {name = 'field7', type = 'boolean'} > +--- > +... > +format[8] = {name = 'field8', type = 'scalar'} > +--- > +... > +format[9] = {name = 'field9', type = 'array'} > +--- > +... > +format[10] = {name = 'field10', type = 'map'} > +--- > +... > +s = box.schema.space.create('test', {engine = engine, format = format}) > +--- > +... > +pk = s:create_index('pk') > +--- > +... > +t = s:replace{1, {2}, 3, '4', 5.5, -6, true, -8, {9, 9}, {val = 10}} > +--- > +... > +inspector:cmd("setopt delimiter ';'") > +--- > +- true > +... > +function fail_format_change(fieldno, new_type) > + local old_type = format[fieldno].type > + format[fieldno].type = new_type > + local ok, msg = pcall(s.format, s, format) > + format[fieldno].type = old_type > + return msg > +end; > +--- > +... > +function ok_format_change(fieldno, new_type) > + local old_type = format[fieldno].type > + format[fieldno].type = new_type > + s:format(format) > + s:delete{1} > + format[fieldno].type = old_type > + s:format(format) > + s:replace(t) > +end; > +--- > +... > +inspector:cmd("setopt delimiter ''"); > +--- > +- true > +... > +-- any --X--> unsigned > +fail_format_change(2, 'unsigned') > +--- > +- 'Tuple field 2 type does not match one required by operation: expected unsigned' > +... > +-- unsigned -----> any > +ok_format_change(3, 'any') > +--- > +... > +-- unsigned --X--> string > +fail_format_change(3, 'string') > +--- > +- 'Tuple field 3 type does not match one required by operation: expected string' > +... > +-- unsigned -----> number > +ok_format_change(3, 'number') > +--- > +... > +-- unsigned -----> integer > +ok_format_change(3, 'integer') > +--- > +... > +-- unsigned -----> scalar > +ok_format_change(3, 'scalar') > +--- > +... > +-- unsigned --X--> map > +fail_format_change(3, 'map') > +--- > +- 'Tuple field 3 type does not match one required by operation: expected map' > +... > +-- string -----> any > +ok_format_change(4, 'any') > +--- > +... > +-- string -----> scalar > +ok_format_change(4, 'scalar') > +--- > +... > +-- string --X--> boolean > +fail_format_change(4, 'boolean') > +--- > +- 'Tuple field 4 type does not match one required by operation: expected boolean' > +... > +-- number -----> any > +ok_format_change(5, 'any') > +--- > +... > +-- number -----> scalar > +ok_format_change(5, 'scalar') > +--- > +... > +-- number --X--> integer > +fail_format_change(5, 'integer') > +--- > +- 'Tuple field 5 type does not match one required by operation: expected integer' > +... > +-- integer -----> any > +ok_format_change(6, 'any') > +--- > +... > +-- integer -----> number > +ok_format_change(6, 'number') > +--- > +... > +-- integer -----> scalar > +ok_format_change(6, 'scalar') > +--- > +... > +-- integer --X--> unsigned > +fail_format_change(6, 'unsigned') > +--- > +- 'Tuple field 6 type does not match one required by operation: expected unsigned' > +... > +-- boolean -----> any > +ok_format_change(7, 'any') > +--- > +... > +-- boolean -----> scalar > +ok_format_change(7, 'scalar') > +--- > +... > +-- boolean --X--> string > +fail_format_change(7, 'string') > +--- > +- 'Tuple field 7 type does not match one required by operation: expected string' > +... > +-- scalar -----> any > +ok_format_change(8, 'any') > +--- > +... > +-- scalar --X--> unsigned > +fail_format_change(8, 'unsigned') > +--- > +- 'Tuple field 8 type does not match one required by operation: expected unsigned' > +... > +-- array -----> any > +ok_format_change(9, 'any') > +--- > +... > +-- array --X--> scalar > +fail_format_change(9, 'scalar') > +--- > +- 'Tuple field 9 type does not match one required by operation: expected scalar' > +... > +-- map -----> any > +ok_format_change(10, 'any') > +--- > +... > +-- map --X--> scalar > +fail_format_change(10, 'scalar') > +--- > +- 'Tuple field 10 type does not match one required by operation: expected scalar' > +... > +s:drop() > +--- > +... > +-- Check new fields adding. > +format = {} > +--- > +... > +s = box.schema.space.create('test', {engine = engine}) > +--- > +... > +format[1] = {name = 'field1', type = 'unsigned'} > +--- > +... > +s:format(format) -- Ok, no indexes. > +--- > +... > +pk = s:create_index('pk') > +--- > +... > +format[2] = {name = 'field2', type = 'unsigned'} > +--- > +... > +s:format(format) -- Ok, empty space. > +--- > +... > +s:replace{1, 1} > +--- > +- [1, 1] > +... > +format[2] = nil > +--- > +... > +s:format(format) -- Ok, can delete fields with no checks. > +--- > +... > +s:drop() > +--- > +... > +s = box.schema.space.create('test', {engine = engine, format = format}) > +--- > +... > +pk = s:create_index('pk') > +--- > +... > +sk1 = s:create_index('sk1', {parts = {2, 'unsigned'}}) > +--- > +... > +sk2 = s:create_index('sk2', {parts = {3, 'unsigned'}}) > +--- > +... > +sk5 = s:create_index('sk5', {parts = {5, 'unsigned'}}) > +--- > +... > +s:replace{1, 1, 1, 1, 1} > +--- > +- [1, 1, 1, 1, 1] > +... > +format[2] = {name = 'field2', type = 'unsigned'} > +--- > +... > +format[3] = {name = 'field3', type = 'unsigned'} > +--- > +... > +format[4] = {name = 'field4', type = 'any'} > +--- > +... > +format[5] = {name = 'field5', type = 'unsigned'} > +--- > +... > +-- Ok, all new fields are indexed or have type ANY, and new > +-- field_count <= old field_count. > +s:format(format) > +--- > +... > +s:replace{1, 1, 1, 1, 1, 1} > +--- > +- [1, 1, 1, 1, 1, 1] > +... > +format[6] = {name = 'field6', type = 'unsigned'} > +--- > +... > +-- Ok, but check existing tuples for a new field[6]. > +s:format(format) > +--- > +... > +-- Fail, not enough fields. > +s:replace{2, 2, 2, 2, 2} > +--- > +- error: Tuple field count 5 is less than required by space format or defined indexes > + (expected at least 6) > +... > +s:replace{2, 2, 2, 2, 2, 2, 2} > +--- > +- [2, 2, 2, 2, 2, 2, 2] > +... > +format[7] = {name = 'field7', type = 'unsigned'} > +--- > +... > +-- Fail, the tuple {1, ... 1} is invalid for a new format. > +s:format(format) > +--- > +- error: Tuple field count 6 is less than required by space format or defined indexes > + (expected at least 7) > +... > +s:drop() > +--- > +... > +-- > +-- Allow to restrict space format, if corresponding restrictions > +-- already are defined in indexes. > +-- > +s = box.schema.space.create('test', {engine = engine}) > +--- > +... > +_ = s:create_index('pk') > +--- > +... > +format = {} > +--- > +... > +format[1] = {name = 'field1'} > +--- > +... > +s:replace{1} > +--- > +- [1] > +... > +s:replace{100} > +--- > +- [100] > +... > +s:replace{0} > +--- > +- [0] > +... > +s:format(format) > +--- > +... > +s:format() > +--- > +- [{'name': 'field1', 'type': 'any'}] > +... > +format[1].type = 'unsigned' > +--- > +... > +s:format(format) > +--- > +... > +s:format() > +--- > +- [{'name': 'field1', 'type': 'unsigned'}] > +... > +s:select() > +--- > +- - [0] > + - [1] > + - [100] > +... > +s:drop() > +--- > +... > diff --git a/test/engine/ddl.test.lua b/test/engine/ddl.test.lua > index 019e18a1..f3d68e1b 100644 > --- a/test/engine/ddl.test.lua > +++ b/test/engine/ddl.test.lua > @@ -125,14 +125,14 @@ space:drop() > -- is omited. > -- > format = {{'field1', 'scalar'}} > -s = box.schema.create_space('test', {format = format}) > +s = box.schema.space.create('test', {engine = engine, format = format}) > pk = s:create_index('pk') > pk.parts[1].type > s:drop() > > -- Ensure type 'any' to be not inherited. > format = {{'field1'}} > -s = box.schema.create_space('test', {format = format}) > +s = box.schema.space.create('test', {engine = engine, format = format}) > pk = s:create_index('pk') > pk.parts[1].type > s:drop() > @@ -141,7 +141,7 @@ s:drop() > -- gh-3229: update optionality if a space format is changed too, > -- not only when indexes are updated. > -- > -s = box.schema.create_space('test', {engine = engine}) > +s = box.schema.space.create('test', {engine = engine}) > format = {} > format[1] = {'field1', 'unsigned'} > format[2] = {'field2', 'unsigned', is_nullable = true} > @@ -165,7 +165,7 @@ s:drop() > -- > -- Modify key definition without index rebuild. > -- > -s = box.schema.create_space('test', {engine = engine}) > +s = box.schema.space.create('test', {engine = engine}) > i1 = s:create_index('i1', {unique = true, parts = {1, 'unsigned'}}) > i2 = s:create_index('i2', {unique = false, parts = {2, 'unsigned'}}) > i3 = s:create_index('i3', {unique = true, parts = {3, 'unsigned'}}) > @@ -195,3 +195,306 @@ i2:select() > i3:select() > > s:drop() > + > +-- > +-- gh-2652: validate space format. > +-- > +s = box.schema.space.create('test', { engine = engine, format = "format" }) > +format = { { name = 100 } } > +s = box.schema.space.create('test', { engine = engine, format = format }) > +long = string.rep('a', box.schema.NAME_MAX + 1) > +format = { { name = long } } > +s = box.schema.space.create('test', { engine = engine, format = format }) > +format = { { name = 'id', type = '100' } } > +s = box.schema.space.create('test', { engine = engine, format = format }) > +format = { setmetatable({}, { __serialize = 'map' }) } > +s = box.schema.space.create('test', { engine = engine, format = format }) > + > +-- Ensure the format is updated after index drop. > +format = { { name = 'id', type = 'unsigned' } } > +s = box.schema.space.create('test', { engine = engine, format = format }) > +pk = s:create_index('pk') > +sk = s:create_index('sk', { parts = { 2, 'string' } }) > +s:replace{1, 1} > +sk:drop() > +s:replace{1, 1} > +s:drop() > + > +-- Check index parts conflicting with space format. > +format = { { name='field1', type='unsigned' }, { name='field2', type='string' }, { name='field3', type='scalar' } } > +s = box.schema.space.create('test', { engine = engine, format = format }) > +pk = s:create_index('pk') > +sk1 = s:create_index('sk1', { parts = { 2, 'unsigned' } }) > + > +-- Check space format conflicting with index parts. > +sk3 = s:create_index('sk3', { parts = { 2, 'string' } }) > +format[2].type = 'unsigned' > +s:format(format) > +s:format() > +s.index.sk3.parts > + > +-- Space format can be updated, if conflicted index is deleted. > +sk3:drop() > +s:format(format) > +s:format() > + > +-- Check deprecated field types. > +format[2].type = 'num' > +format[3].type = 'str' > +format[4] = { name = 'field4', type = '*' } > +format > +s:format(format) > +s:format() > +s:replace{1, 2, '3', {4, 4, 4}} > + > +-- Check not indexed fields checking. > +s:truncate() > +format[2] = {name='field2', type='string'} > +format[3] = {name='field3', type='array'} > +format[4] = {name='field4', type='number'} > +format[5] = {name='field5', type='integer'} > +format[6] = {name='field6', type='scalar'} > +format[7] = {name='field7', type='map'} > +format[8] = {name='field8', type='any'} > +format[9] = {name='field9'} > +s:format(format) > + > +-- Check incorrect field types. > +format[9] = {name='err', type='any'} > +s:format(format) > + > +s:replace{1, '2', {3, 3}, 4.4, -5, true, {value=7}, 8, 9} > +s:replace{1, 2, {3, 3}, 4.4, -5, true, {value=7}, 8, 9} > +s:replace{1, '2', 3, 4.4, -5, true, {value=7}, 8, 9} > +s:replace{1, '2', {3, 3}, '4', -5, true, {value=7}, 8, 9} > +s:replace{1, '2', {3, 3}, 4.4, -5.5, true, {value=7}, 8, 9} > +s:replace{1, '2', {3, 3}, 4.4, -5, {6, 6}, {value=7}, 8, 9} > +s:replace{1, '2', {3, 3}, 4.4, -5, true, {7}, 8, 9} > +s:replace{1, '2', {3, 3}, 4.4, -5, true, {value=7}} > +s:replace{1, '2', {3, 3}, 4.4, -5, true, {value=7}, 8} > +s:truncate() > + > +-- > +-- gh-1014: field names. > +-- > +format = {} > +format[1] = {name = 'field1', type = 'unsigned'} > +format[2] = {name = 'field2'} > +format[3] = {name = 'field1'} > +s:format(format) > + > +s:drop() > + > +-- https://github.com/tarantool/tarantool/issues/2815 > +-- Extend space format definition syntax > +format = {{name='key',type='unsigned'}, {name='value',type='string'}} > +s = box.schema.space.create('test', { engine = engine, format = format }) > +s:format() > +s:format({'id', 'name'}) > +s:format() > +s:format({'id', {'name1'}}) > +s:format() > +s:format({'id', {'name2', 'string'}}) > +s:format() > +s:format({'id', {'name', type = 'string'}}) > +s:format() > +s:drop() > + > +format = {'key', {'value',type='string'}} > +s = box.schema.space.create('test', { engine = engine, format = format }) > +s:format() > +s:drop() > + > +s = box.schema.space.create('test', { engine = engine }) > +s:create_index('test', {parts = {'test'}}) > +s:create_index('test', {parts = {{'test'}}}) > +s:create_index('test', {parts = {{field = 'test'}}}) > +s:create_index('test', {parts = {1}}).parts > +s:drop() > + > +s = box.schema.space.create('test', { engine = engine }) > +s:format{{'test1', 'integer'}, 'test2', {'test3', 'integer'}, {'test4','scalar'}} > +s:create_index('test', {parts = {'test'}}) > +s:create_index('test', {parts = {{'test'}}}) > +s:create_index('test', {parts = {{field = 'test'}}}) > +s:create_index('test1', {parts = {'test1'}}).parts > +s:create_index('test2', {parts = {'test2'}}).parts > +s:create_index('test3', {parts = {{'test1', 'integer'}}}).parts > +s:create_index('test4', {parts = {{'test2', 'integer'}}}).parts > +s:create_index('test5', {parts = {{'test2', 'integer'}}}).parts > +s:create_index('test6', {parts = {1, 3}}).parts > +s:create_index('test7', {parts = {'test1', 4}}).parts > +s:create_index('test8', {parts = {{1, 'integer'}, {'test4', 'scalar'}}}).parts > +s:drop() > + > +-- > +-- gh-2800: space formats checking is broken. > +-- > + > +-- Ensure that vinyl correctly process field count change. > +s = box.schema.space.create('test', {engine = engine, field_count = 2}) > +pk = s:create_index('pk') > +s:replace{1, 2} > +t = box.space._space:select{s.id}[1]:totable() > +t[5] = 1 > +box.space._space:replace(t) > +s:drop() > + > +-- Check field type changes. > +format = {} > +format[1] = {name = 'field1', type = 'unsigned'} > +format[2] = {name = 'field2', type = 'any'} > +format[3] = {name = 'field3', type = 'unsigned'} > +format[4] = {name = 'field4', type = 'string'} > +format[5] = {name = 'field5', type = 'number'} > +format[6] = {name = 'field6', type = 'integer'} > +format[7] = {name = 'field7', type = 'boolean'} > +format[8] = {name = 'field8', type = 'scalar'} > +format[9] = {name = 'field9', type = 'array'} > +format[10] = {name = 'field10', type = 'map'} > +s = box.schema.space.create('test', {engine = engine, format = format}) > +pk = s:create_index('pk') > +t = s:replace{1, {2}, 3, '4', 5.5, -6, true, -8, {9, 9}, {val = 10}} > + > +inspector:cmd("setopt delimiter ';'") > +function fail_format_change(fieldno, new_type) > + local old_type = format[fieldno].type > + format[fieldno].type = new_type > + local ok, msg = pcall(s.format, s, format) > + format[fieldno].type = old_type > + return msg > +end; > + > +function ok_format_change(fieldno, new_type) > + local old_type = format[fieldno].type > + format[fieldno].type = new_type > + s:format(format) > + s:delete{1} > + format[fieldno].type = old_type > + s:format(format) > + s:replace(t) > +end; > +inspector:cmd("setopt delimiter ''"); > + > +-- any --X--> unsigned > +fail_format_change(2, 'unsigned') > + > +-- unsigned -----> any > +ok_format_change(3, 'any') > +-- unsigned --X--> string > +fail_format_change(3, 'string') > +-- unsigned -----> number > +ok_format_change(3, 'number') > +-- unsigned -----> integer > +ok_format_change(3, 'integer') > +-- unsigned -----> scalar > +ok_format_change(3, 'scalar') > +-- unsigned --X--> map > +fail_format_change(3, 'map') > + > +-- string -----> any > +ok_format_change(4, 'any') > +-- string -----> scalar > +ok_format_change(4, 'scalar') > +-- string --X--> boolean > +fail_format_change(4, 'boolean') > + > +-- number -----> any > +ok_format_change(5, 'any') > +-- number -----> scalar > +ok_format_change(5, 'scalar') > +-- number --X--> integer > +fail_format_change(5, 'integer') > + > +-- integer -----> any > +ok_format_change(6, 'any') > +-- integer -----> number > +ok_format_change(6, 'number') > +-- integer -----> scalar > +ok_format_change(6, 'scalar') > +-- integer --X--> unsigned > +fail_format_change(6, 'unsigned') > + > +-- boolean -----> any > +ok_format_change(7, 'any') > +-- boolean -----> scalar > +ok_format_change(7, 'scalar') > +-- boolean --X--> string > +fail_format_change(7, 'string') > + > +-- scalar -----> any > +ok_format_change(8, 'any') > +-- scalar --X--> unsigned > +fail_format_change(8, 'unsigned') > + > +-- array -----> any > +ok_format_change(9, 'any') > +-- array --X--> scalar > +fail_format_change(9, 'scalar') > + > +-- map -----> any > +ok_format_change(10, 'any') > +-- map --X--> scalar > +fail_format_change(10, 'scalar') > + > +s:drop() > + > +-- Check new fields adding. > +format = {} > +s = box.schema.space.create('test', {engine = engine}) > +format[1] = {name = 'field1', type = 'unsigned'} > +s:format(format) -- Ok, no indexes. > +pk = s:create_index('pk') > +format[2] = {name = 'field2', type = 'unsigned'} > +s:format(format) -- Ok, empty space. > +s:replace{1, 1} > +format[2] = nil > +s:format(format) -- Ok, can delete fields with no checks. > +s:drop() > + > +s = box.schema.space.create('test', {engine = engine, format = format}) > +pk = s:create_index('pk') > +sk1 = s:create_index('sk1', {parts = {2, 'unsigned'}}) > +sk2 = s:create_index('sk2', {parts = {3, 'unsigned'}}) > +sk5 = s:create_index('sk5', {parts = {5, 'unsigned'}}) > +s:replace{1, 1, 1, 1, 1} > +format[2] = {name = 'field2', type = 'unsigned'} > +format[3] = {name = 'field3', type = 'unsigned'} > +format[4] = {name = 'field4', type = 'any'} > +format[5] = {name = 'field5', type = 'unsigned'} > +-- Ok, all new fields are indexed or have type ANY, and new > +-- field_count <= old field_count. > +s:format(format) > + > +s:replace{1, 1, 1, 1, 1, 1} > +format[6] = {name = 'field6', type = 'unsigned'} > +-- Ok, but check existing tuples for a new field[6]. > +s:format(format) > + > +-- Fail, not enough fields. > +s:replace{2, 2, 2, 2, 2} > + > +s:replace{2, 2, 2, 2, 2, 2, 2} > +format[7] = {name = 'field7', type = 'unsigned'} > +-- Fail, the tuple {1, ... 1} is invalid for a new format. > +s:format(format) > +s:drop() > + > +-- > +-- Allow to restrict space format, if corresponding restrictions > +-- already are defined in indexes. > +-- > +s = box.schema.space.create('test', {engine = engine}) > +_ = s:create_index('pk') > +format = {} > +format[1] = {name = 'field1'} > +s:replace{1} > +s:replace{100} > +s:replace{0} > +s:format(format) > +s:format() > +format[1].type = 'unsigned' > +s:format(format) > +s:format() > +s:select() > +s:drop() > diff --git a/test/vinyl/ddl.result b/test/vinyl/ddl.result > index 5142f0f2..4607a44e 100644 > --- a/test/vinyl/ddl.result > +++ b/test/vinyl/ddl.result > @@ -696,89 +696,6 @@ index = space:create_index('test', { type = 'tree', parts = { 2, 'map' }}) > space:drop() > --- > ... > --- > --- Allow compatible changes of a non-empty vinyl space. > --- > -space = box.schema.create_space('test', { engine = 'vinyl' }) > ---- > -... > -pk = space:create_index('primary') > ---- > -... > -space:replace{1} > ---- > -- [1] > -... > -space:replace{2} > ---- > -- [2] > -... > -format = {} > ---- > -... > -format[1] = {name = 'field1'} > ---- > -... > -format[2] = {name = 'field2', is_nullable = true} > ---- > -... > -format[3] = {name = 'field3', is_nullable = true} > ---- > -... > -space:format(format) > ---- > -... > -t1 = space:replace{3,4,5} > ---- > -... > -t2 = space:replace{4,5} > ---- > -... > -t1.field1, t1.field2, t1.field3 > ---- > -- 3 > -- 4 > -- 5 > -... > -t2.field1, t2.field2, t2.field3 > ---- > -- 4 > -- 5 > -- null > -... > -t1 = pk:get{1} > ---- > -... > -t1.field1, t1.field2, t1.field3 > ---- > -- 1 > -- null > -- null > -... > -box.snapshot() > ---- > -- ok > -... > -t1 = pk:get{2} > ---- > -... > -t1.field1, t1.field2, t1.field3 > ---- > -- 2 > -- null > -- null > -... > --- Forbid incompatible change. > -format[2].is_nullable = false > ---- > -... > -space:format(format) > ---- > -- error: Vinyl does not support changing format of a non-empty space > -... > -space:drop() > ---- > -... > -- gh-3019 default index options > box.space._space:insert{512, 1, 'test', 'vinyl', 0, setmetatable({}, {__serialize = 'map'}), {}} > --- > diff --git a/test/vinyl/ddl.test.lua b/test/vinyl/ddl.test.lua > index c4bd36bb..637a331d 100644 > --- a/test/vinyl/ddl.test.lua > +++ b/test/vinyl/ddl.test.lua > @@ -260,32 +260,6 @@ index = space:create_index('test', { type = 'tree', parts = { 2, 'array' }}) > index = space:create_index('test', { type = 'tree', parts = { 2, 'map' }}) > space:drop() > > --- > --- Allow compatible changes of a non-empty vinyl space. > --- > -space = box.schema.create_space('test', { engine = 'vinyl' }) > -pk = space:create_index('primary') > -space:replace{1} > -space:replace{2} > -format = {} > -format[1] = {name = 'field1'} > -format[2] = {name = 'field2', is_nullable = true} > -format[3] = {name = 'field3', is_nullable = true} > -space:format(format) > -t1 = space:replace{3,4,5} > -t2 = space:replace{4,5} > -t1.field1, t1.field2, t1.field3 > -t2.field1, t2.field2, t2.field3 > -t1 = pk:get{1} > -t1.field1, t1.field2, t1.field3 > -box.snapshot() > -t1 = pk:get{2} > -t1.field1, t1.field2, t1.field3 > --- Forbid incompatible change. > -format[2].is_nullable = false > -space:format(format) > -space:drop() > - > -- gh-3019 default index options > box.space._space:insert{512, 1, 'test', 'vinyl', 0, setmetatable({}, {__serialize = 'map'}), {}} > box.space._index:insert{512, 0, 'pk', 'tree', {unique = true}, {{0, 'unsigned'}}} > diff --git a/test/vinyl/errinj.result b/test/vinyl/errinj.result > index 91351086..fd21f7bb 100644 > --- a/test/vinyl/errinj.result > +++ b/test/vinyl/errinj.result > @@ -1320,3 +1320,78 @@ ret > s:drop() > --- > ... > +-- > +-- Check that ALTER is abroted if a tuple inserted during space > +-- format change does not conform to the new format. > +-- > +format = {} > +--- > +... > +format[1] = {name = 'field1', type = 'unsigned'} > +--- > +... > +format[2] = {name = 'field2', type = 'string', is_nullable = true} > +--- > +... > +s = box.schema.space.create('test', {engine = 'vinyl', format = format}) > +--- > +... > +_ = s:create_index('pk', {page_size = 16}) > +--- > +... > +pad = string.rep('x', 16) > +--- > +... > +for i = 101, 200 do s:replace{i, pad} end > +--- > +... > +box.snapshot() > +--- > +- ok > +... > +ch = fiber.channel(1) > +--- > +... > +test_run:cmd("setopt delimiter ';'") > +--- > +- true > +... > +_ = fiber.create(function() > + fiber.sleep(0.01) > + for i = 1, 100 do > + s:replace{i, box.NULL} > + end > + ch:put(true) > +end); > +--- > +... > +test_run:cmd("setopt delimiter ''"); > +--- > +- true > +... > +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) > +--- > +- ok > +... > +format[2].is_nullable = false > +--- > +... > +s:format(format) -- must fail > +--- > +- error: 'Tuple field 2 type does not match one required by operation: expected string' > +... > +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) > +--- > +- ok > +... > +ch:get() > +--- > +- true > +... > +s:count() -- 200 > +--- > +- 200 > +... > +s:drop() > +--- > +... > diff --git a/test/vinyl/errinj.test.lua b/test/vinyl/errinj.test.lua > index 9724a69b..64d04c62 100644 > --- a/test/vinyl/errinj.test.lua > +++ b/test/vinyl/errinj.test.lua > @@ -513,3 +513,38 @@ errinj.set("ERRINJ_VY_DELAY_PK_LOOKUP", false) > while ret == nil do fiber.sleep(0.01) end > ret > s:drop() > + > +-- > +-- Check that ALTER is abroted if a tuple inserted during space > +-- format change does not conform to the new format. > +-- > +format = {} > +format[1] = {name = 'field1', type = 'unsigned'} > +format[2] = {name = 'field2', type = 'string', is_nullable = true} > +s = box.schema.space.create('test', {engine = 'vinyl', format = format}) > +_ = s:create_index('pk', {page_size = 16}) > + > +pad = string.rep('x', 16) > +for i = 101, 200 do s:replace{i, pad} end > +box.snapshot() > + > +ch = fiber.channel(1) > +test_run:cmd("setopt delimiter ';'") > +_ = fiber.create(function() > + fiber.sleep(0.01) > + for i = 1, 100 do > + s:replace{i, box.NULL} > + end > + ch:put(true) > +end); > +test_run:cmd("setopt delimiter ''"); > + > +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0.001) > +format[2].is_nullable = false > +s:format(format) -- must fail > +errinj.set("ERRINJ_VY_READ_PAGE_TIMEOUT", 0) > + > +ch:get() > + > +s:count() -- 200 > +s:drop() > -- > 2.11.0 > -- Konstantin Osipov, Moscow, Russia, +7 903 626 22 32 http://tarantool.io - www.twitter.com/kostja_osipov