diff --git a/src/box/alter.cc b/src/box/alter.cc index 9055eda77cc67496d4963d93fc24f76ac0d1450a..1364f5ea82b751fb70828935bd19f2044dc15266 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -79,6 +79,21 @@ access_check_ddl(uint32_t owner_uid, enum schema_object_type type) } } +/** + * Throw an exception if the given index definition + * is incompatible with a sequence. + */ +static void +index_def_check_sequence(struct index_def *index_def, const char *space_name) +{ + enum field_type type = index_def->key_def->parts[0].type; + if (type != FIELD_TYPE_UNSIGNED && type != FIELD_TYPE_INTEGER) { + tnt_raise(ClientError, ER_MODIFY_INDEX, index_def->name, + space_name, "sequence cannot be used with " + "a non-integer key"); + } +} + /** * Support function for index_def_new_from_tuple(..) * Checks tuple (of _index space) and throws a nice error if it is invalid @@ -407,6 +422,8 @@ index_def_new_from_tuple(struct tuple *tuple, struct space *old_space) auto index_def_guard = make_scoped_guard([=] { index_def_delete(index_def); }); index_def_check_xc(index_def, space_name(old_space)); old_space->handler->checkIndexDef(old_space, index_def); + if (old_space->sequence != NULL) + index_def_check_sequence(index_def, space_name(old_space)); index_def_guard.is_active = false; return index_def; } @@ -910,9 +927,11 @@ alter_space_do(struct txn *txn, struct alter_space *alter) alter->new_space->handler->prepareAlterSpace(alter->old_space, alter->new_space); + alter->new_space->sequence = alter->old_space->sequence; alter->new_space->truncate_count = alter->old_space->truncate_count; memcpy(alter->new_space->access, alter->old_space->access, sizeof(alter->old_space->access)); + /* * Change the new space: build the new index, rename, * change the fixed field count. @@ -1633,6 +1652,15 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event) tnt_raise(ClientError, ER_DROP_PRIMARY_KEY, space_name(old_space)); } + /* + * Can't drop primary key before space sequence. + */ + if (old_space->sequence != NULL) { + tnt_raise(ClientError, ER_ALTER_SPACE, + space_name(old_space), + "can not drop primary key while " + "space sequence exists"); + } } if (iid != 0 && space_index(old_space, 0) == NULL) { @@ -1839,6 +1867,9 @@ on_replace_dd_truncate(struct trigger * /* trigger */, void *event) /* Preserve the access control lists during truncate. */ memcpy(new_space->access, old_space->access, sizeof(old_space->access)); + /* Truncate does not affect space sequence. */ + new_space->sequence = old_space->sequence; + /* * Replace the old space with the new one in the space * cache. Requests processed after this point will see @@ -2690,6 +2721,9 @@ on_replace_dd_sequence(struct trigger * /* trigger */, void *event) if (space_has_data(BOX_SEQUENCE_DATA_ID, 0, id)) tnt_raise(ClientError, ER_DROP_SEQUENCE, seq->def->name, "the sequence has data"); + if (space_has_data(BOX_SPACE_SEQUENCE_ID, 1, id)) + tnt_raise(ClientError, ER_DROP_SEQUENCE, + seq->def->name, "the sequence is in use"); alter->old_def = seq->def; } else { /* UPDATE */ new_def = sequence_def_new_from_tuple(new_tuple, @@ -2737,6 +2771,50 @@ on_replace_dd_sequence_data(struct trigger * /* trigger */, void *event) } } +/** + * Run the triggers registered on commit of a change in _space. + */ +static void +on_commit_dd_space_sequence(struct trigger *trigger, void * /* event */) +{ + struct space *space = (struct space *) trigger->data; + trigger_run(&on_alter_space, space); +} + +/** + * A trigger invoked on replace in space _space_sequence. + * Used to update space <-> sequence mapping. + */ +static void +on_replace_dd_space_sequence(struct trigger * /* trigger */, void *event) +{ + struct txn *txn = (struct txn *) event; + txn_check_singlestatement(txn, "Space _space_sequence"); + struct txn_stmt *stmt = txn_current_stmt(txn); + struct tuple *tuple = stmt->new_tuple ?: stmt->old_tuple; + + uint32_t space_id = tuple_field_u32_xc(tuple, + BOX_SPACE_SEQUENCE_FIELD_ID); + uint32_t sequence_id = tuple_field_u32_xc(tuple, + BOX_SPACE_SEQUENCE_FIELD_SEQUENCE_ID); + + struct space *space = space_cache_find(space_id); + struct sequence *seq = sequence_cache_find(sequence_id); + + struct trigger *on_commit = + txn_alter_trigger_new(on_commit_dd_space_sequence, space); + txn_on_commit(txn, on_commit); + + if (stmt->new_tuple != NULL) { /* INSERT, UPDATE */ + struct Index *pk = index_find(space, 0); + index_def_check_sequence(pk->index_def, space_name(space)); + space->sequence = seq; + } else { /* DELETE */ + assert(space->sequence == seq); + space->sequence = NULL; + } +} + /* }}} sequence */ static void @@ -2803,6 +2881,10 @@ struct trigger on_replace_sequence_data = { RLIST_LINK_INITIALIZER, on_replace_dd_sequence_data, NULL, NULL }; +struct trigger on_replace_space_sequence = { + RLIST_LINK_INITIALIZER, on_replace_dd_space_sequence, NULL, NULL +}; + struct trigger on_stmt_begin_space = { RLIST_LINK_INITIALIZER, lock_before_dd, NULL, NULL }; diff --git a/src/box/alter.h b/src/box/alter.h index 4835a0c7c90ae50f3e03513c06a266d0df8f8eb1..423648793603e006bf98ce3a97f508f184893245 100644 --- a/src/box/alter.h +++ b/src/box/alter.h @@ -42,6 +42,7 @@ extern struct trigger on_replace_priv; extern struct trigger on_replace_cluster; extern struct trigger on_replace_sequence; extern struct trigger on_replace_sequence_data; +extern struct trigger on_replace_space_sequence; extern struct trigger on_stmt_begin_space; extern struct trigger on_stmt_begin_index; extern struct trigger on_stmt_begin_truncate; diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap index 1977f0c8a3cda0165b0e67ae23f4e520907eefaf..a8098ed560e3aace1126cf15c73c36f80d4287ad 100644 Binary files a/src/box/bootstrap.snap and b/src/box/bootstrap.snap differ diff --git a/src/box/box.cc b/src/box/box.cc index e834d15ed3b9db1723450a5791a7cccc96ae15a3..cac0cea086b2138e322ba7205f0ecd4e8e0bf75d 100644 --- a/src/box/box.cc +++ b/src/box/box.cc @@ -170,6 +170,91 @@ request_rebind_to_primary_key(struct request *request, struct space *space, request->header = NULL; } +/** + * Handle INSERT/REPLACE in a space with a sequence attached. + */ +static void +request_handle_sequence(struct request *request, struct space *space) +{ + struct sequence *seq = space->sequence; + + assert(seq != NULL); + assert(request->type == IPROTO_INSERT || + request->type == IPROTO_REPLACE); + + struct Index *pk = space_index(space, 0); + if (unlikely(pk == NULL)) + return; + + /* + * Look up the first field of the primary key. + */ + const char *data = request->tuple; + const char *data_end = request->tuple_end; + int len = mp_decode_array(&data); + int fieldno = pk->index_def->key_def->parts[0].fieldno; + if (unlikely(len < fieldno + 1)) + return; + + const char *key = data; + if (unlikely(fieldno > 0)) { + do { + mp_next(&key); + } while (--fieldno > 0); + } + + int64_t value; + if (mp_typeof(*key) == MP_NIL) { + /* + * If the first field of the primary key is nil, + * this is an auto increment request and we need + * to replace the nil with the next value generated + * by the space sequence. + */ + if (unlikely(sequence_next(seq, &value) != 0)) + diag_raise(); + + const char *key_end = key; + mp_decode_nil(&key_end); + + size_t buf_size = (request->tuple_end - request->tuple) + + mp_sizeof_uint(UINT64_MAX); + char *tuple = (char *) region_alloc_xc(&fiber()->gc, buf_size); + char *tuple_end = mp_encode_array(tuple, len); + + if (unlikely(key != data)) { + memcpy(tuple_end, data, key - data); + tuple_end += key - data; + } + + if (value >= 0) + tuple_end = mp_encode_uint(tuple_end, value); + else + tuple_end = mp_encode_int(tuple_end, value); + + memcpy(tuple_end, key_end, data_end - key_end); + tuple_end += data_end - key_end; + + assert(tuple_end <= tuple + buf_size); + + request->tuple = tuple; + request->tuple_end = tuple_end; + } else { + /* + * If the first field is not nil, update the space + * sequence with its value, to make sure that an + * auto increment request never tries to insert a + * value that is already in the space. Note, this + * code is also invoked on final recovery to restore + * the sequence value from WAL. + */ + if (likely(mp_read_int64(&key, &value) == 0)) { + if (sequence_update(seq, value) != 0) + diag_raise(); + } + } +} + static void process_rw(struct request *request, struct space *space, struct tuple **result) { @@ -182,6 +267,8 @@ process_rw(struct request *request, struct space *space, struct tuple **result) switch (request->type) { case IPROTO_INSERT: case IPROTO_REPLACE: + if (space->sequence != NULL) + request_handle_sequence(request, space); tuple = space->handler->executeReplace(txn, space, request); diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua index 3bc9bb6ac3802a01ac328092cb449dc16e17d925..79f87449094cce41b5bc1eea3c7d681040f90929 100644 --- a/src/box/lua/schema.lua +++ b/src/box/lua/schema.lua @@ -397,6 +397,12 @@ box.schema.space.drop = function(space_id, space_name, opts) local _index = box.space[box.schema.INDEX_ID] local _priv = box.space[box.schema.PRIV_ID] local _truncate = box.space[box.schema.TRUNCATE_ID] + local _space_sequence = box.space[box.schema.SPACE_SEQUENCE_ID] + local sequence_tuple = _space_sequence:delete{space_id} + if sequence_tuple ~= nil and sequence_tuple[3] == true then + -- Delete automatically generated sequence. + box.schema.sequence.drop(sequence_tuple[2]) + end local keys = _index:select(space_id) for i = #keys, 1, -1 do local v = keys[i] @@ -491,6 +497,7 @@ local alter_index_template = { name = 'string', type = 'string', parts = 'table', + sequence = 'boolean, number, string', } for k, v in pairs(index_options) do alter_index_template[k] = v @@ -591,14 +598,49 @@ box.schema.index.create = function(space_id, name, options) "please use '%s' instead", field_type, part[2]) end end + local _space_sequence = box.space[box.schema.SPACE_SEQUENCE_ID] + local sequence_is_generated = false + local sequence = options.sequence or nil -- ignore sequence = false + if sequence ~= nil then + if iid ~= 0 then + box.error(box.error.MODIFY_INDEX, name, box.space[space_id].name, + "sequence cannot be used with a secondary key") + end + if #parts >= 1 and parts[1][2] ~= 'integer' and + parts[1][2] ~= 'unsigned' then + box.error(box.error.MODIFY_INDEX, name, box.space[space_id].name, + "sequence cannot be used with a non-integer key") + end + if sequence == true then + sequence = box.schema.sequence.create( + box.space[space_id].name .. '_seq') + sequence = sequence.id + sequence_is_generated = true + else + sequence = sequence_resolve(sequence) + if sequence == nil then + box.error(box.error.NO_SUCH_SEQUENCE, options.sequence) + end + end + end _index:insert{space_id, iid, name, options.type, index_opts, parts} + if sequence ~= nil then + _space_sequence:insert{space_id, sequence, sequence_is_generated} + end return box.space[space_id].index[name] end box.schema.index.drop = function(space_id, index_id) check_param(space_id, 'space_id', 'number') check_param(index_id, 'index_id', 'number') - + if index_id == 0 then + local _space_sequence = box.space[box.schema.SPACE_SEQUENCE_ID] + local sequence_tuple = _space_sequence:delete{space_id} + if sequence_tuple ~= nil and sequence_tuple[3] == true then + -- Delete automatically generated sequence. + box.schema.sequence.drop(sequence_tuple[2]) + end + end local _index = box.space[box.schema.INDEX_ID] _index:delete{space_id, index_id} end @@ -695,8 +737,52 @@ box.schema.index.alter = function(space_id, index_id, options) table.insert(parts, {options.parts[i], options.parts[i + 1]}) end end + local _space_sequence = box.space[box.schema.SPACE_SEQUENCE_ID] + local sequence_is_generated = false + local sequence = options.sequence + local sequence_tuple = _space_sequence:get(space_id) + if sequence or (sequence ~= false and sequence_tuple ~= nil) then + if index_id ~= 0 then + box.error(box.error.MODIFY_INDEX, + options.name, box.space[space_id].name, + "sequence cannot be used with a secondary key") + end + if #parts >= 1 and parts[1][2] ~= 'integer' and + parts[1][2] ~= 'unsigned' then + box.error(box.error.MODIFY_INDEX, + options.name, box.space[space_id].name, + "sequence cannot be used with a non-integer key") + end + end + if sequence == true then + if sequence_tuple == nil or sequence_tuple[3] == false then + sequence = box.schema.sequence.create( + box.space[space_id].name .. '_seq') + sequence = sequence.id + sequence_is_generated = true + else + -- Space already has an automatically generated sequence. + sequence = nil + end + elseif sequence then + sequence = sequence_resolve(sequence) + if sequence == nil then + box.error(box.error.NO_SUCH_SEQUENCE, options.sequence) + end + end + if sequence == false then + _space_sequence:delete(space_id) + end _index:replace{space_id, index_id, options.name, options.type, index_opts, parts} + if sequence then + _space_sequence:replace{space_id, sequence, sequence_is_generated} + end + if sequence_tuple ~= nil and sequence_tuple[3] == true and + sequence_tuple[2] ~= sequence then + -- Delete automatically generated sequence. + box.schema.sequence.drop(sequence_tuple[2]) + end end -- a static box_tuple_t ** instance for calling box_index_* API diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc index ecaa6bccb6dca010c3e2aac4aec85dea5e8db6f2..94a88c7e58e6a87d6fe1d91949a12593d9c155dd 100644 --- a/src/box/lua/space.cc +++ b/src/box/lua/space.cc @@ -45,6 +45,7 @@ extern "C" { #include "box/tuple.h" #include "box/txn.h" #include "box/vclock.h" /* VCLOCK_MAX */ +#include "box/sequence.h" /** * Trigger function for all spaces @@ -205,6 +206,11 @@ lbox_fillspace(struct lua_State *L, struct space *space, int i) lua_settable(L, -3); /* space.index[k].parts */ + if (k == 0 && space->sequence != NULL) { + lua_pushnumber(L, space->sequence->def->id); + lua_setfield(L, -2, "sequence_id"); + } + if (space_is_vinyl(space)) { lua_pushstring(L, "options"); lua_newtable(L); @@ -359,6 +365,8 @@ box_lua_space_init(struct lua_State *L) lua_setfield(L, -2, "SEQUENCE_ID"); lua_pushnumber(L, BOX_SEQUENCE_DATA_ID); lua_setfield(L, -2, "SEQUENCE_DATA_ID"); + lua_pushnumber(L, BOX_SPACE_SEQUENCE_ID); + lua_setfield(L, -2, "SPACE_SEQUENCE_ID"); lua_pushnumber(L, BOX_SYSTEM_ID_MIN); lua_setfield(L, -2, "SYSTEM_ID_MIN"); lua_pushnumber(L, BOX_SYSTEM_ID_MAX); diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua index fb5d1dd06c0e0c704d9d4817a22797fec6333267..537e282c84291acbfe20a12356f49a2182425053 100644 --- a/src/box/lua/upgrade.lua +++ b/src/box/lua/upgrade.lua @@ -844,6 +844,7 @@ local function create_sequence_space() local _index = box.space[box.schema.INDEX_ID] local _sequence = box.space[box.schema.SEQUENCE_ID] local _sequence_data = box.space[box.schema.SEQUENCE_DATA_ID] + local _space_sequence = box.space[box.schema.SPACE_SEQUENCE_ID] local MAP = setmap({}) log.info("create space _sequence") @@ -869,6 +870,16 @@ local function create_sequence_space() {{name = 'id', type = 'unsigned'}, {name = 'value', type = 'integer'}}} log.info("create index primary on _sequence_data") _index:insert{_sequence_data.id, 0, 'primary', 'hash', {unique = true}, {{0, 'unsigned'}}} + + log.info("create space _space_sequence") + _space:insert{_space_sequence.id, ADMIN, '_space_sequence', 'memtx', 0, MAP, + {{name = 'id', type = 'unsigned'}, + {name = 'sequence_id', type = 'unsigned'}, + {name = 'is_generated', type = 'boolean'}}} + log.info("create index _space_sequence:primary") + _index:insert{_space_sequence.id, 0, 'primary', 'tree', {unique = true}, {{0, 'unsigned'}}} + log.info("create index _space_sequence:sequence") + _index:insert{_space_sequence.id, 1, 'sequence', 'tree', {unique = false}, {{1, 'unsigned'}}} end local function upgrade_to_1_7_6() diff --git a/src/box/schema.cc b/src/box/schema.cc index 794a2cba066ee2facaae3daba9061ab0e3ce788c..225b7212002ffe0c11818f61979fdca72b0999f4 100644 --- a/src/box/schema.cc +++ b/src/box/schema.cc @@ -294,6 +294,10 @@ schema_init() sc_space_new(BOX_SEQUENCE_DATA_ID, "_sequence_data", key_def, &on_replace_sequence_data, NULL); + /* _space_seq - association space <-> sequence. */ + sc_space_new(BOX_SPACE_SEQUENCE_ID, "_space_sequence", key_def, + &on_replace_space_sequence, NULL); + /* _user - all existing users */ sc_space_new(BOX_USER_ID, "_user", key_def, &on_replace_user, NULL); diff --git a/src/box/schema_def.h b/src/box/schema_def.h index 046dd5306fcb3e04b395ae7165c45ef9d7c3cf93..47a717328c9bb31d28e8355b4e1a7b441179fa61 100644 --- a/src/box/schema_def.h +++ b/src/box/schema_def.h @@ -96,6 +96,8 @@ enum { BOX_CLUSTER_ID = 320, /** Space id of _truncate. */ BOX_TRUNCATE_ID = 330, + /** Space id of _space_sequence. */ + BOX_SPACE_SEQUENCE_ID = 340, /** End of the reserved range of system spaces. */ BOX_SYSTEM_ID_MAX = 511, BOX_ID_NIL = 2147483647 @@ -189,6 +191,13 @@ enum { BOX_SEQUENCE_DATA_FIELD_VALUE = 1, }; +/** _space_seq fields. */ +enum { + BOX_SPACE_SEQUENCE_FIELD_ID = 0, + BOX_SPACE_SEQUENCE_FIELD_SEQUENCE_ID = 1, + BOX_SPACE_SEQUENCE_FIELD_IS_GENERATED = 2, +}; + /* * Different objects which can be subject to access * control. diff --git a/src/box/sequence.c b/src/box/sequence.c index 93a938369409a187ebc4f1f4b9c174864218fd80..8da31823c96fea853f9a89eaaac629abeb5916da 100644 --- a/src/box/sequence.c +++ b/src/box/sequence.c @@ -121,6 +121,31 @@ sequence_set(struct sequence *seq, int64_t value) return -1; } +int +sequence_update(struct sequence *seq, int64_t value) +{ + uint32_t key = seq->def->id; + uint32_t hash = sequence_hash(key); + uint32_t pos = light_sequence_find_key(&sequence_data_index, hash, key); + struct sequence_data new_data, data; + new_data.id = key; + new_data.value = value; + if (pos != light_sequence_end) { + data = light_sequence_get(&sequence_data_index, pos); + if ((seq->def->step > 0 && value > data.value) || + (seq->def->step < 0 && value < data.value)) { + if (light_sequence_replace(&sequence_data_index, hash, + new_data, &data) == light_sequence_end) + unreachable(); + } + } else { + if (light_sequence_insert(&sequence_data_index, hash, + new_data) == light_sequence_end) + return -1; + } + return 0; +} + int sequence_next(struct sequence *seq, int64_t *result) { diff --git a/src/box/sequence.h b/src/box/sequence.h index 08d452f010aa28161e0c0897c0c757b02b0a74e1..dcfcf528f7bce68809d211b83ad4f850df856493 100644 --- a/src/box/sequence.h +++ b/src/box/sequence.h @@ -141,6 +141,15 @@ sequence_reset(struct sequence *seq); int sequence_set(struct sequence *seq, int64_t value); +/** + * Update the sequence if the given value is newer than + * the last generated value. + * + * Return 0 on success, -1 on memory allocation failure. + */ +int +sequence_update(struct sequence *seq, int64_t value); + /** * Advance a sequence. * diff --git a/src/box/space.h b/src/box/space.h index ca0a48727f9f9bfdae6b4a8a5cde663165266077..b09644787dc0352deed0e75c323bcb9cfe0721b5 100644 --- a/src/box/space.h +++ b/src/box/space.h @@ -41,6 +41,7 @@ extern "C" { struct Index; struct Handler; +struct sequence; struct space { struct access access[BOX_USER_MAX]; @@ -72,6 +73,8 @@ struct space { uint32_t index_id_max; /** Space meta. */ struct space_def *def; + /** Sequence attached to this space or NULL. */ + struct sequence *sequence; /** * Number of times the space has been truncated. * Updating this counter via _truncate space triggers diff --git a/test/app-tap/tarantoolctl.test.lua b/test/app-tap/tarantoolctl.test.lua index c1982bed663bf24f01575d5b636f5d707f89a4ea..38945ec5ba89cd678a243c164e4073ed1162786d 100755 --- a/test/app-tap/tarantoolctl.test.lua +++ b/test/app-tap/tarantoolctl.test.lua @@ -338,8 +338,8 @@ do check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 1", "\n", 3) check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 1 --replica 2", "\n", 3) check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 2", "\n", 0) - check_ctlcat_snap(test_i, dir, "--space=280", "---\n", 15) - check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 36) + check_ctlcat_snap(test_i, dir, "--space=280", "---\n", 16) + check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 38) end) end) diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result index d6ef7d692b8d707060d06e254670c48d43779790..0994b084dbf5e31d4d3dea76479fb0c39eb159f1 100644 --- a/test/box-py/bootstrap.result +++ b/test/box-py/bootstrap.result @@ -57,6 +57,8 @@ box.space._space:select{} 'type': 'string'}]] - [330, 1, '_truncate', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'count', 'type': 'unsigned'}]] + - [340, 1, '_space_sequence', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, + {'name': 'sequence_id', 'type': 'unsigned'}, {'name': 'is_generated', 'type': 'boolean'}]] ... box.space._index:select{} --- @@ -98,6 +100,8 @@ box.space._index:select{} - [320, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]] - [320, 1, 'uuid', 'tree', {'unique': true}, [[1, 'string']]] - [330, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]] + - [340, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]] + - [340, 1, 'sequence', 'tree', {'unique': false}, [[1, 'unsigned']]] ... box.space._user:select{} --- diff --git a/test/box/access_misc.result b/test/box/access_misc.result index a1080c5dae243499d8059cd70b11fbfdc871ada1..2c585b06558ae0469f491b208f7907ffbeddf80e 100644 --- a/test/box/access_misc.result +++ b/test/box/access_misc.result @@ -78,7 +78,7 @@ s:delete(1) ... s:drop() --- -- error: Read access is denied for user 'testus' to space '_index' +- error: Write access is denied for user 'testus' to space '_space_sequence' ... -- -- Check double revoke @@ -126,7 +126,7 @@ s:insert({3}) ... s:drop() --- -- error: Read access is denied for user 'testus' to space '_index' +- error: Write access is denied for user 'testus' to space '_space_sequence' ... session.su('admin') --- @@ -169,7 +169,7 @@ s:delete({3}) ... s:drop() --- -- error: Read access is denied for user 'guest' to space '_index' +- error: Write access is denied for user 'guest' to space '_space_sequence' ... gs = box.schema.space.create('guest_space') --- @@ -666,6 +666,8 @@ box.space._space:select() 'type': 'string'}]] - [330, 1, '_truncate', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'count', 'type': 'unsigned'}]] + - [340, 1, '_space_sequence', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, + {'name': 'sequence_id', 'type': 'unsigned'}, {'name': 'is_generated', 'type': 'boolean'}]] ... box.space._func:select() --- diff --git a/test/box/access_sysview.result b/test/box/access_sysview.result index faf3f1a5dab828f197ca6e6d6aa854c2a872dd74..99ab3b954cada177450728806bc5002027166b6e 100644 --- a/test/box/access_sysview.result +++ b/test/box/access_sysview.result @@ -230,11 +230,11 @@ box.session.su('guest') ... #box.space._vspace:select{} --- -- 16 +- 17 ... #box.space._vindex:select{} --- -- 37 +- 39 ... #box.space._vuser:select{} --- @@ -262,7 +262,7 @@ box.session.su('guest') ... #box.space._vindex:select{} --- -- 37 +- 39 ... #box.space._vuser:select{} --- diff --git a/test/box/alter.result b/test/box/alter.result index 3bf1c1a10dde0ea5fb7ab12871880ff76ea867ba..b46e703f4ea7f88101d64343479e1bb10d3004ab 100644 --- a/test/box/alter.result +++ b/test/box/alter.result @@ -107,7 +107,7 @@ space = box.space[t[1]] ... space.id --- -- 331 +- 341 ... space.field_count --- @@ -152,7 +152,7 @@ space_deleted ... space:replace{0} --- -- error: Space '331' does not exist +- error: Space '341' does not exist ... _index:insert{_space.id, 0, 'primary', 'tree', 1, 1, 0, 'unsigned'} --- @@ -210,6 +210,8 @@ _index:select{} - [320, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]] - [320, 1, 'uuid', 'tree', {'unique': true}, [[1, 'string']]] - [330, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]] + - [340, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]] + - [340, 1, 'sequence', 'tree', {'unique': false}, [[1, 'unsigned']]] ... -- modify indexes of a system space _index:delete{_index.id, 0} diff --git a/test/box/sequence.result b/test/box/sequence.result index cd5dd471bd06d58b9e8cc1f8cead3a2cdb185c5b..3210c7667a15130b531e264cf47d1a5a99222c49 100644 --- a/test/box/sequence.result +++ b/test/box/sequence.result @@ -563,8 +563,562 @@ s:drop() --- ... -- +-- Attaching a sequence to a space. +-- +-- Index create/modify checks. +s = box.schema.space.create('test') +--- +... +sq = box.schema.sequence.create('test') +--- +... +sq:set(123) +--- +... +s:create_index('pk', {parts = {1, 'string'}, sequence = 'test'}) -- error +--- +- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot + be used with a non-integer key' +... +s:create_index('pk', {parts = {1, 'scalar'}, sequence = 'test'}) -- error +--- +- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot + be used with a non-integer key' +... +s:create_index('pk', {parts = {1, 'number'}, sequence = 'test'}) -- error +--- +- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot + be used with a non-integer key' +... +pk = s:create_index('pk', {parts = {1, 'integer'}, sequence = 'test'}) -- ok +--- +... +pk:drop() +--- +... +pk = s:create_index('pk', {parts = {1, 'unsigned'}, sequence = 'test'}) -- ok +--- +... +pk:drop() +--- +... +pk = s:create_index('pk') -- ok +--- +... +s:create_index('secondary', {parts = {2, 'unsigned'}, sequence = 'test'}) -- error +--- +- error: 'Can''t create or modify index ''secondary'' in space ''test'': sequence + cannot be used with a secondary key' +... +pk:drop() +--- +... +pk = s:create_index('pk', {parts = {1, 'unsigned'}, sequence = 'test'}) -- ok +--- +... +pk:alter{parts = {1, 'string'}} -- error +--- +- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot + be used with a non-integer key' +... +box.space._index:update({s.id, pk.id}, {{'=', 6, {{0, 'string'}}}}) -- error +--- +- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot + be used with a non-integer key' +... +box.space._index:delete{s.id, pk.id} -- error +--- +- error: 'Can''t modify space ''test'': can not drop primary key while space sequence + exists' +... +pk:alter{parts = {1, 'string'}, sequence = false} -- ok +--- +... +sk = s:create_index('sk', {parts = {2, 'unsigned'}}) +--- +... +sk:alter{sequence = 'test'} -- error +--- +- error: 'Can''t create or modify index ''sk'' in space ''test'': sequence cannot + be used with a secondary key' +... +box.space._space_sequence:insert{s.id, sq.id, false} -- error +--- +- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot + be used with a non-integer key' +... +sk:drop() +--- +... +pk:drop() +--- +... +s:create_index('pk', {sequence = {}}) -- error +--- +- error: 'Illegal parameters, options parameter ''sequence'' should be one of types: + boolean, number, string' +... +s:create_index('pk', {sequence = 'abc'}) -- error +--- +- error: Sequence 'abc' does not exist +... +s:create_index('pk', {sequence = 12345}) -- error +--- +- error: Sequence '12345' does not exist +... +pk = s:create_index('pk', {sequence = 'test'}) -- ok +--- +... +s.index.pk.sequence_id == sq.id +--- +- true +... +pk:drop() +--- +... +pk = s:create_index('pk', {sequence = sq.id}) -- ok +--- +... +s.index.pk.sequence_id == sq.id +--- +- true +... +pk:drop() +--- +... +pk = s:create_index('pk', {sequence = false}) -- ok +--- +... +s.index.pk.sequence_id == nil +--- +- true +... +pk:alter{sequence = {}} -- error +--- +- error: 'Illegal parameters, options parameter ''sequence'' should be one of types: + boolean, number, string' +... +pk:alter{sequence = 'abc'} -- error +--- +- error: Sequence 'abc' does not exist +... +pk:alter{sequence = 12345} -- error +--- +- error: Sequence '12345' does not exist +... +pk:alter{sequence = 'test'} -- ok +--- +... +s.index.pk.sequence_id == sq.id +--- +- true +... +pk:alter{sequence = sq.id} -- ok +--- +... +s.index.pk.sequence_id == sq.id +--- +- true +... +pk:alter{sequence = false} -- ok +--- +... +s.index.pk.sequence_id == nil +--- +- true +... +pk:drop() +--- +... +sq:next() -- 124 +--- +- 124 +... +sq:drop() +--- +... +s:drop() +--- +... +-- Using a sequence for auto increment. +sq = box.schema.sequence.create('test') +--- +... +s1 = box.schema.space.create('test1') +--- +... +_ = s1:create_index('pk', {parts = {1, 'unsigned'}, sequence = 'test'}) +--- +... +s2 = box.schema.space.create('test2') +--- +... +_ = s2:create_index('pk', {parts = {2, 'integer'}, sequence = 'test'}) +--- +... +s3 = box.schema.space.create('test3') +--- +... +_ = s3:create_index('pk', {parts = {2, 'unsigned', 1, 'string'}, sequence = 'test'}) +--- +... +s1:insert(box.tuple.new(nil)) -- 1 +--- +- [1] +... +s2:insert(box.tuple.new('a', nil)) -- 2 +--- +- ['a', 2] +... +s3:insert(box.tuple.new('b', nil)) -- 3 +--- +- ['b', 3] +... +s1:truncate() +--- +... +s2:truncate() +--- +... +s3:truncate() +--- +... +s1:insert{nil, 123, 456} -- 4 +--- +- [4, 123, 456] +... +s2:insert{'c', nil, 123} -- 5 +--- +- ['c', 5, 123] +... +s3:insert{'d', nil, 456} -- 6 +--- +- ['d', 6, 456] +... +sq:next() -- 7 +--- +- 7 +... +sq:reset() +--- +... +s1:insert{nil, nil, 'aa'} -- 1 +--- +- [1, null, 'aa'] +... +s2:insert{'bb', nil, nil, 'cc'} -- 2 +--- +- ['bb', 2, null, 'cc'] +... +s3:insert{'dd', nil, nil, 'ee'} -- 3 +--- +- ['dd', 3, null, 'ee'] +... +sq:next() -- 4 +--- +- 4 +... +sq:set(100) +--- +... +s1:insert{nil, 'aaa', 1} -- 101 +--- +- [101, 'aaa', 1] +... +s2:insert{'bbb', nil, 2} -- 102 +--- +- ['bbb', 102, 2] +... +s3:insert{'ccc', nil, 3} -- 103 +--- +- ['ccc', 103, 3] +... +sq:next() -- 104 +--- +- 104 +... +s1:insert{1000, 'xxx'} +--- +- [1000, 'xxx'] +... +sq:next() -- 1001 +--- +- 1001 +... +s2:insert{'yyy', 2000} +--- +- ['yyy', 2000] +... +sq:next() -- 2001 +--- +- 2001 +... +s3:insert{'zzz', 3000} +--- +- ['zzz', 3000] +... +sq:next() -- 3001 +--- +- 3001 +... +s1:insert{500, 'xxx'} +--- +- [500, 'xxx'] +... +s3:insert{'zzz', 2500} +--- +- ['zzz', 2500] +... +s2:insert{'yyy', 1500} +--- +- ['yyy', 1500] +... +sq:next() -- 3002 +--- +- 3002 +... +sq:drop() -- error +--- +- error: 'Can''t drop sequence ''test'': the sequence is in use' +... +s1:drop() +--- +... +sq:drop() -- error +--- +- error: 'Can''t drop sequence ''test'': the sequence is in use' +... +s2:drop() +--- +... +sq:drop() -- error +--- +- error: 'Can''t drop sequence ''test'': the sequence is in use' +... +s3:drop() +--- +... +sq:drop() -- ok +--- +... +-- Automatically generated sequences. +s = box.schema.space.create('test') +--- +... +sq = box.schema.sequence.create('test') +--- +... +sq:set(123) +--- +... +pk = s:create_index('pk', {sequence = true}) +--- +... +sq = box.sequence.test_seq +--- +... +sq.step, sq.min, sq.max, sq.start, sq.cycle +--- +- 1 +- 1 +- 9223372036854775807 +- 1 +- false +... +s.index.pk.sequence_id == sq.id +--- +- true +... +s:insert{nil, 'a'} -- 1 +--- +- [1, 'a'] +... +s:insert{nil, 'b'} -- 2 +--- +- [2, 'b'] +... +s:insert{nil, 'c'} -- 3 +--- +- [3, 'c'] +... +sq:next() -- 4 +--- +- 4 +... +pk:alter{sequence = false} +--- +... +s.index.pk.sequence_id == nil +--- +- true +... +s:insert{nil, 'x'} -- error +--- +- error: 'Tuple field 1 type does not match one required by operation: expected unsigned' +... +box.sequence.test_seq == nil +--- +- true +... +pk:alter{sequence = true} +--- +... +sq.step, sq.min, sq.max, sq.start, sq.cycle +--- +- 1 +- 1 +- 9223372036854775807 +- 1 +- false +... +sq = box.sequence.test_seq +--- +... +s.index.pk.sequence_id == sq.id +--- +- true +... +s:insert{100, 'abc'} +--- +- [100, 'abc'] +... +s:insert{nil, 'cda'} -- 101 +--- +- [101, 'cda'] +... +sq:next() -- 102 +--- +- 102 +... +pk:alter{sequence = 'test'} +--- +... +s.index.pk.sequence_id == box.sequence.test.id +--- +- true +... +box.sequence.test_seq == nil +--- +- true +... +pk:alter{sequence = true} +--- +... +s.index.pk.sequence_id == box.sequence.test_seq.id +--- +- true +... +pk:drop() +--- +... +box.sequence.test_seq == nil +--- +- true +... +pk = s:create_index('pk', {sequence = true}) +--- +... +s.index.pk.sequence_id == box.sequence.test_seq.id +--- +- true +... +s:drop() +--- +... +box.sequence.test_seq == nil +--- +- true +... +sq = box.sequence.test +--- +... +sq:next() -- 124 +--- +- 124 +... +sq:drop() +--- +... +-- Sequences are compatible with Vinyl spaces. +s = box.schema.space.create('test', {engine = 'vinyl'}) +--- +... +_ = s:create_index('pk', {sequence = true}) +--- +... +s:insert{nil, 'a'} -- 1 +--- +- [1, 'a'] +... +s:insert{100, 'b'} -- 100 +--- +- [100, 'b'] +... +box.begin() +--- +... +s:insert{nil, 'c'} -- 101 +--- +- [101, 'c'] +... +s:insert{nil, 'd'} -- 102 +--- +- [102, 'd'] +... +box.rollback() +--- +... +box.begin() +--- +... +s:insert{nil, 'e'} -- 103 +--- +- [103, 'e'] +... +s:insert{nil, 'f'} -- 104 +--- +- [104, 'f'] +... +box.commit() +--- +... +s:select() -- {1, 'a'}, {100, 'b'}, {103, 'e'}, {104, 'f'} +--- +- - [1, 'a'] + - [100, 'b'] + - [103, 'e'] + - [104, 'f'] +... +s:drop() +--- +... +-- -- Check that sequences are persistent. -- +s1 = box.schema.space.create('test1') +--- +... +_ = s1:create_index('pk', {sequence = true}) +--- +... +s1:insert{nil, 'a'} -- 1 +--- +- [1, 'a'] +... +box.snapshot() +--- +- ok +... +s2 = box.schema.space.create('test2') +--- +... +_ = s2:create_index('pk', {sequence = true}) +--- +... +s2:insert{101, 'aaa'} +--- +- [101, 'aaa'] +... sq = box.schema.sequence.create('test', {step = 2, min = 10, max = 20, start = 15, cycle = true}) --- ... @@ -591,3 +1145,31 @@ sq:next() sq:drop() --- ... +s1 = box.space.test1 +--- +... +s1.index.pk.sequence_id == box.sequence.test1_seq.id +--- +- true +... +s1:insert{nil, 'b'} -- 2 +--- +- [2, 'b'] +... +s1:drop() +--- +... +s2 = box.space.test2 +--- +... +s2.index.pk.sequence_id == box.sequence.test2_seq.id +--- +- true +... +s2:insert{nil, 'bbb'} -- 102 +--- +- [102, 'bbb'] +... +s2:drop() +--- +... diff --git a/test/box/sequence.test.lua b/test/box/sequence.test.lua index ef49bd0e5758555a1f6236c7d4ff62eaa8f7d6b5..1b4f2fbcf27dec6fa54f6118fe207940e61239d1 100644 --- a/test/box/sequence.test.lua +++ b/test/box/sequence.test.lua @@ -183,10 +183,192 @@ sq1:drop() sq2:drop() s:drop() +-- +-- Attaching a sequence to a space. +-- + +-- Index create/modify checks. +s = box.schema.space.create('test') +sq = box.schema.sequence.create('test') +sq:set(123) + +s:create_index('pk', {parts = {1, 'string'}, sequence = 'test'}) -- error +s:create_index('pk', {parts = {1, 'scalar'}, sequence = 'test'}) -- error +s:create_index('pk', {parts = {1, 'number'}, sequence = 'test'}) -- error + +pk = s:create_index('pk', {parts = {1, 'integer'}, sequence = 'test'}) -- ok +pk:drop() +pk = s:create_index('pk', {parts = {1, 'unsigned'}, sequence = 'test'}) -- ok +pk:drop() + +pk = s:create_index('pk') -- ok +s:create_index('secondary', {parts = {2, 'unsigned'}, sequence = 'test'}) -- error +pk:drop() + +pk = s:create_index('pk', {parts = {1, 'unsigned'}, sequence = 'test'}) -- ok +pk:alter{parts = {1, 'string'}} -- error +box.space._index:update({s.id, pk.id}, {{'=', 6, {{0, 'string'}}}}) -- error +box.space._index:delete{s.id, pk.id} -- error +pk:alter{parts = {1, 'string'}, sequence = false} -- ok +sk = s:create_index('sk', {parts = {2, 'unsigned'}}) +sk:alter{sequence = 'test'} -- error +box.space._space_sequence:insert{s.id, sq.id, false} -- error +sk:drop() +pk:drop() + +s:create_index('pk', {sequence = {}}) -- error +s:create_index('pk', {sequence = 'abc'}) -- error +s:create_index('pk', {sequence = 12345}) -- error +pk = s:create_index('pk', {sequence = 'test'}) -- ok +s.index.pk.sequence_id == sq.id +pk:drop() +pk = s:create_index('pk', {sequence = sq.id}) -- ok +s.index.pk.sequence_id == sq.id +pk:drop() +pk = s:create_index('pk', {sequence = false}) -- ok +s.index.pk.sequence_id == nil +pk:alter{sequence = {}} -- error +pk:alter{sequence = 'abc'} -- error +pk:alter{sequence = 12345} -- error +pk:alter{sequence = 'test'} -- ok +s.index.pk.sequence_id == sq.id +pk:alter{sequence = sq.id} -- ok +s.index.pk.sequence_id == sq.id +pk:alter{sequence = false} -- ok +s.index.pk.sequence_id == nil +pk:drop() + +sq:next() -- 124 +sq:drop() +s:drop() + +-- Using a sequence for auto increment. +sq = box.schema.sequence.create('test') +s1 = box.schema.space.create('test1') +_ = s1:create_index('pk', {parts = {1, 'unsigned'}, sequence = 'test'}) +s2 = box.schema.space.create('test2') +_ = s2:create_index('pk', {parts = {2, 'integer'}, sequence = 'test'}) +s3 = box.schema.space.create('test3') +_ = s3:create_index('pk', {parts = {2, 'unsigned', 1, 'string'}, sequence = 'test'}) + +s1:insert(box.tuple.new(nil)) -- 1 +s2:insert(box.tuple.new('a', nil)) -- 2 +s3:insert(box.tuple.new('b', nil)) -- 3 +s1:truncate() +s2:truncate() +s3:truncate() +s1:insert{nil, 123, 456} -- 4 +s2:insert{'c', nil, 123} -- 5 +s3:insert{'d', nil, 456} -- 6 +sq:next() -- 7 +sq:reset() +s1:insert{nil, nil, 'aa'} -- 1 +s2:insert{'bb', nil, nil, 'cc'} -- 2 +s3:insert{'dd', nil, nil, 'ee'} -- 3 +sq:next() -- 4 +sq:set(100) +s1:insert{nil, 'aaa', 1} -- 101 +s2:insert{'bbb', nil, 2} -- 102 +s3:insert{'ccc', nil, 3} -- 103 +sq:next() -- 104 +s1:insert{1000, 'xxx'} +sq:next() -- 1001 +s2:insert{'yyy', 2000} +sq:next() -- 2001 +s3:insert{'zzz', 3000} +sq:next() -- 3001 +s1:insert{500, 'xxx'} +s3:insert{'zzz', 2500} +s2:insert{'yyy', 1500} +sq:next() -- 3002 + +sq:drop() -- error +s1:drop() +sq:drop() -- error +s2:drop() +sq:drop() -- error +s3:drop() +sq:drop() -- ok + +-- Automatically generated sequences. +s = box.schema.space.create('test') +sq = box.schema.sequence.create('test') +sq:set(123) + +pk = s:create_index('pk', {sequence = true}) +sq = box.sequence.test_seq +sq.step, sq.min, sq.max, sq.start, sq.cycle +s.index.pk.sequence_id == sq.id +s:insert{nil, 'a'} -- 1 +s:insert{nil, 'b'} -- 2 +s:insert{nil, 'c'} -- 3 +sq:next() -- 4 + +pk:alter{sequence = false} +s.index.pk.sequence_id == nil +s:insert{nil, 'x'} -- error +box.sequence.test_seq == nil + +pk:alter{sequence = true} +sq.step, sq.min, sq.max, sq.start, sq.cycle +sq = box.sequence.test_seq +s.index.pk.sequence_id == sq.id +s:insert{100, 'abc'} +s:insert{nil, 'cda'} -- 101 +sq:next() -- 102 + +pk:alter{sequence = 'test'} +s.index.pk.sequence_id == box.sequence.test.id +box.sequence.test_seq == nil + +pk:alter{sequence = true} +s.index.pk.sequence_id == box.sequence.test_seq.id +pk:drop() +box.sequence.test_seq == nil + +pk = s:create_index('pk', {sequence = true}) +s.index.pk.sequence_id == box.sequence.test_seq.id +s:drop() +box.sequence.test_seq == nil + +sq = box.sequence.test +sq:next() -- 124 +sq:drop() + +-- Sequences are compatible with Vinyl spaces. +s = box.schema.space.create('test', {engine = 'vinyl'}) +_ = s:create_index('pk', {sequence = true}) + +s:insert{nil, 'a'} -- 1 +s:insert{100, 'b'} -- 100 + +box.begin() +s:insert{nil, 'c'} -- 101 +s:insert{nil, 'd'} -- 102 +box.rollback() + +box.begin() +s:insert{nil, 'e'} -- 103 +s:insert{nil, 'f'} -- 104 +box.commit() + +s:select() -- {1, 'a'}, {100, 'b'}, {103, 'e'}, {104, 'f'} +s:drop() + -- -- Check that sequences are persistent. -- +s1 = box.schema.space.create('test1') +_ = s1:create_index('pk', {sequence = true}) +s1:insert{nil, 'a'} -- 1 + +box.snapshot() + +s2 = box.schema.space.create('test2') +_ = s2:create_index('pk', {sequence = true}) +s2:insert{101, 'aaa'} + sq = box.schema.sequence.create('test', {step = 2, min = 10, max = 20, start = 15, cycle = true}) sq:next() @@ -197,3 +379,13 @@ sq.step, sq.min, sq.max, sq.start, sq.cycle sq:next() sq:drop() + +s1 = box.space.test1 +s1.index.pk.sequence_id == box.sequence.test1_seq.id +s1:insert{nil, 'b'} -- 2 +s1:drop() + +s2 = box.space.test2 +s2.index.pk.sequence_id == box.sequence.test2_seq.id +s2:insert{nil, 'bbb'} -- 102 +s2:drop() diff --git a/test/engine/iterator.result b/test/engine/iterator.result index 96516f7b18dbc8fe9ee9a05551d4ad00413565e3..260eeb29aa19d60b763f2411eb54ca041ee3611f 100644 --- a/test/engine/iterator.result +++ b/test/engine/iterator.result @@ -4212,7 +4212,7 @@ s:replace{35} ... state, value = gen(param,state) --- -- error: 'builtin/box/schema.lua:743: usage: next(param, state)' +- error: 'builtin/box/schema.lua:829: usage: next(param, state)' ... value --- diff --git a/test/wal_off/alter.result b/test/wal_off/alter.result index 7c5ae26f9089b184876bafc34e80a8f08e9d1f70..7ac001ec0bd3da6906dd99abf906b5d170f0b094 100644 --- a/test/wal_off/alter.result +++ b/test/wal_off/alter.result @@ -28,7 +28,7 @@ end; ... #spaces; --- -- 65518 +- 65517 ... -- cleanup for k, v in pairs(spaces) do diff --git a/test/xlog/upgrade.result b/test/xlog/upgrade.result index 8e366a1481d1349b031069cd9838c02b84fb4354..dd08961bc967963467b30a2c1131d7c22a160ace 100644 --- a/test/xlog/upgrade.result +++ b/test/xlog/upgrade.result @@ -84,6 +84,8 @@ box.space._space:select() 'type': 'string'}]] - [330, 1, '_truncate', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'count', 'type': 'unsigned'}]] + - [340, 1, '_space_sequence', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, + {'name': 'sequence_id', 'type': 'unsigned'}, {'name': 'is_generated', 'type': 'boolean'}]] - [512, 1, 'distro', 'memtx', 0, {}, [{'name': 'os', 'type': 'str'}, {'name': 'dist', 'type': 'str'}, {'name': 'version', 'type': 'num'}, {'name': 'time', 'type': 'num'}]] - [513, 1, 'temporary', 'memtx', 0, {'temporary': true}, []] @@ -128,6 +130,8 @@ box.space._index:select() - [320, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]] - [320, 1, 'uuid', 'tree', {'unique': true}, [[1, 'string']]] - [330, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]] + - [340, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]] + - [340, 1, 'sequence', 'tree', {'unique': false}, [[1, 'unsigned']]] - [512, 0, 'primary', 'hash', {'unique': true}, [[0, 'string'], [1, 'string'], [ 2, 'unsigned']]] - [512, 1, 'codename', 'hash', {'unique': true}, [[1, 'string']]]