From: Vladimir Davydov <vdavydov.dev@gmail.com> To: kostja@tarantool.org Cc: tarantool-patches@freelists.org Subject: [PATCH 12/12] vinyl: allow to modify format of non-empty spaces Date: Sat, 7 Apr 2018 16:38:09 +0300 [thread overview] Message-ID: <d274c9290186f6431572729a2ca03c2286deb561.1523105106.git.vdavydov.dev@gmail.com> (raw) In-Reply-To: <cover.1523105106.git.vdavydov.dev@gmail.com> In-Reply-To: <cover.1523105106.git.vdavydov.dev@gmail.com> 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. --- 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; + } + 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
next prev parent reply other threads:[~2018-04-07 13:38 UTC|newest] Thread overview: 34+ messages / expand[flat|nested] mbox.gz Atom feed top 2018-04-07 13:37 [PATCH 00/12] " Vladimir Davydov 2018-04-07 13:37 ` [PATCH 01/12] alter: introduce CheckSpaceFormat AlterSpaceOp for validating format Vladimir Davydov 2018-04-09 20:25 ` Konstantin Osipov 2018-04-07 13:37 ` [PATCH 02/12] alter: fold ModifySpaceFormat into ModifySpace Vladimir Davydov 2018-04-09 20:26 ` Konstantin Osipov 2018-04-07 13:38 ` [PATCH 03/12] alter: move dictionary update from ModifySpace::alter_def to alter Vladimir Davydov 2018-04-09 20:32 ` Konstantin Osipov 2018-04-10 7:53 ` Vladimir Davydov 2018-04-10 11:45 ` Vladimir Davydov 2018-04-07 13:38 ` [PATCH 04/12] alter: use space_index instead of index_find where appropriate Vladimir Davydov 2018-04-09 20:34 ` Konstantin Osipov 2018-04-07 13:38 ` [PATCH 05/12] alter: allocate triggers before the point of no return Vladimir Davydov 2018-04-09 20:36 ` Konstantin Osipov 2018-04-10 7:57 ` Vladimir Davydov 2018-04-10 11:54 ` Vladimir Davydov 2018-04-07 13:38 ` [PATCH 06/12] space: space_vtab::build_secondary_key => build_index Vladimir Davydov 2018-04-09 20:39 ` Konstantin Osipov 2018-04-10 8:05 ` Vladimir Davydov 2018-04-10 12:14 ` Vladimir Davydov 2018-04-07 13:38 ` [PATCH 07/12] space: pass new format instead of new space to space_vtab::check_format Vladimir Davydov 2018-04-09 20:40 ` Konstantin Osipov 2018-04-07 13:38 ` [PATCH 08/12] alter: introduce preparation phase Vladimir Davydov 2018-04-09 20:46 ` [tarantool-patches] " Konstantin Osipov 2018-04-10 8:31 ` Vladimir Davydov 2018-04-10 8:46 ` Konstantin Osipov 2018-04-07 13:38 ` [PATCH 09/12] alter: zap space_def_check_compatibility Vladimir Davydov 2018-04-09 20:49 ` Konstantin Osipov 2018-04-07 13:38 ` [PATCH 10/12] vinyl: remove superfluous ddl checks Vladimir Davydov 2018-04-09 20:49 ` Konstantin Osipov 2018-04-07 13:38 ` [PATCH 11/12] vinyl: force index rebuild if indexed field type is narrowed Vladimir Davydov 2018-04-09 20:51 ` Konstantin Osipov 2018-04-07 13:38 ` Vladimir Davydov [this message] 2018-04-09 8:24 ` [PATCH 12/12] vinyl: allow to modify format of non-empty spaces Vladimir Davydov 2018-04-09 20:55 ` Konstantin Osipov
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=d274c9290186f6431572729a2ca03c2286deb561.1523105106.git.vdavydov.dev@gmail.com \ --to=vdavydov.dev@gmail.com \ --cc=kostja@tarantool.org \ --cc=tarantool-patches@freelists.org \ --subject='Re: [PATCH 12/12] vinyl: allow to modify format of non-empty spaces' \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox