diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt index 1b1d3a01305986af510c951b84d8e4682d5bb1d4..f043ef7772f950ce762283b4665c2d0205462043 100644 --- a/src/box/CMakeLists.txt +++ b/src/box/CMakeLists.txt @@ -82,6 +82,7 @@ add_library(box STATIC vy_read_set.c space.cc space_def.c + sequence.c func.c func_def.c alter.cc @@ -113,6 +114,7 @@ add_library(box STATIC lua/slab.c lua/index.c lua/space.cc + lua/sequence.c lua/misc.cc lua/info.c lua/stat.c diff --git a/src/box/alter.cc b/src/box/alter.cc index 0bae54aad88a2cbb4d36750718c882cbea3c6a33..dfb725cccabd8a2c485ce6c04ea44b8db5d636b3 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -49,6 +49,7 @@ #include "iproto_constants.h" #include "memtx_tuple.h" #include "version.h" +#include "sequence.h" /** * chap-sha1 of empty string, i.e. @@ -1868,12 +1869,13 @@ bool user_has_data(struct user *user) { uint32_t uid = user->def->uid; - uint32_t spaces[] = { BOX_SPACE_ID, BOX_FUNC_ID, BOX_PRIV_ID, BOX_PRIV_ID }; + uint32_t spaces[] = { BOX_SPACE_ID, BOX_FUNC_ID, BOX_SEQUENCE_ID, + BOX_PRIV_ID, BOX_PRIV_ID }; /* * owner index id #1 for _space and _func and _priv. * For _priv also check that the user has no grants. */ - uint32_t indexes[] = { 1, 1, 1, 0 }; + uint32_t indexes[] = { 1, 1, 1, 1, 0 }; uint32_t count = sizeof(spaces)/sizeof(*spaces); for (uint32_t i = 0; i < count; i++) { if (space_has_data(spaces[i], indexes[i], uid)) @@ -2562,6 +2564,175 @@ on_replace_dd_cluster(struct trigger *trigger, void *event) /* }}} cluster configuration */ +/* {{{ sequence */ + +/** Create a sequence definition from a tuple. */ +static struct sequence_def * +sequence_def_new_from_tuple(struct tuple *tuple, uint32_t errcode) +{ + uint32_t name_len; + const char *name = tuple_field_str_xc(tuple, BOX_USER_FIELD_NAME, + &name_len); + if (name_len > BOX_NAME_MAX) { + tnt_raise(ClientError, errcode, + tt_cstr(name, BOX_INVALID_NAME_MAX), + "sequence name is too long"); + } + size_t sz = sequence_def_sizeof(name_len); + struct sequence_def *def = (struct sequence_def *) malloc(sz); + if (def == NULL) + tnt_raise(OutOfMemory, sz, "malloc", "sequence"); + auto def_guard = make_scoped_guard([=] { free(def); }); + memcpy(def->name, name, name_len); + def->name[name_len] = '\0'; + def->id = tuple_field_u32_xc(tuple, BOX_SEQUENCE_FIELD_ID); + def->uid = tuple_field_u32_xc(tuple, BOX_SEQUENCE_FIELD_UID); + def->step = tuple_field_i64_xc(tuple, BOX_SEQUENCE_FIELD_STEP); + def->min = tuple_field_i64_xc(tuple, BOX_SEQUENCE_FIELD_MIN); + def->max = tuple_field_i64_xc(tuple, BOX_SEQUENCE_FIELD_MAX); + def->start = tuple_field_i64_xc(tuple, BOX_SEQUENCE_FIELD_START); + def->cache = tuple_field_i64_xc(tuple, BOX_SEQUENCE_FIELD_CACHE); + def->cycle = tuple_field_bool_xc(tuple, BOX_SEQUENCE_FIELD_CYCLE); + if (def->step == 0) + tnt_raise(ClientError, errcode, def->name, + "step option must be non-zero"); + if (def->min > def->max) + tnt_raise(ClientError, errcode, def->name, + "max must be greater than or equal to min"); + if (def->start < def->min || def->start > def->max) + tnt_raise(ClientError, errcode, def->name, + "start must be between min and max"); + def_guard.is_active = false; + return def; +} + +/** Argument passed to on_commit_dd_sequence() trigger. */ +struct alter_sequence { + /** Trigger invoked on commit in the _sequence space. */ + struct trigger on_commit; + /** Trigger invoked on rollback in the _sequence space. */ + struct trigger on_rollback; + /** Old sequence definition or NULL if create. */ + struct sequence_def *old_def; + /** New sequence defitition or NULL if drop. */ + struct sequence_def *new_def; +}; + +/** + * Trigger invoked on commit in the _sequence space. + */ +static void +on_commit_dd_sequence(struct trigger *trigger, void *event) +{ + struct txn *txn = (struct txn *) event; + struct alter_sequence *alter = (struct alter_sequence *) trigger->data; + + if (alter->new_def != NULL && alter->old_def != NULL) { + /* Alter a sequence. */ + sequence_cache_replace(alter->new_def); + } else if (alter->new_def == NULL) { + /* Drop a sequence. */ + sequence_cache_delete(alter->old_def->id); + } + + trigger_run(&on_alter_sequence, txn_last_stmt(txn)); +} + +/** + * Trigger invoked on rollback in the _sequence space. + */ +static void +on_rollback_dd_sequence(struct trigger *trigger, void * /* event */) +{ + struct alter_sequence *alter = (struct alter_sequence *) trigger->data; + + if (alter->new_def != NULL && alter->old_def == NULL) { + /* Rollback creation of a sequence. */ + sequence_cache_delete(alter->new_def->id); + } +} + +/** + * A trigger invoked on replace in space _sequence. + * Used to alter a sequence definition. + */ +static void +on_replace_dd_sequence(struct trigger * /* trigger */, void *event) +{ + struct txn *txn = (struct txn *) event; + txn_check_singlestatement(txn, "Space _sequence"); + struct txn_stmt *stmt = txn_current_stmt(txn); + struct tuple *old_tuple = stmt->old_tuple; + struct tuple *new_tuple = stmt->new_tuple; + + struct alter_sequence *alter = + region_calloc_object_xc(&fiber()->gc, struct alter_sequence); + + struct sequence_def *new_def = NULL; + auto def_guard = make_scoped_guard([=] { free(new_def); }); + + if (old_tuple == NULL && new_tuple != NULL) { /* INSERT */ + new_def = sequence_def_new_from_tuple(new_tuple, + ER_CREATE_SEQUENCE); + assert(sequence_by_id(new_def->id) == NULL); + sequence_cache_replace(new_def); + alter->new_def = new_def; + } else if (old_tuple != NULL && new_tuple == NULL) { /* DELETE */ + uint32_t id = tuple_field_u32_xc(old_tuple, + BOX_SEQUENCE_DATA_FIELD_ID); + struct sequence *seq = sequence_by_id(id); + assert(seq != NULL); + if (space_has_data(BOX_SEQUENCE_DATA_ID, 0, id)) + tnt_raise(ClientError, ER_DROP_SEQUENCE, + seq->def->name, "the sequence has data"); + alter->old_def = seq->def; + } else { /* UPDATE */ + new_def = sequence_def_new_from_tuple(new_tuple, + ER_ALTER_SEQUENCE); + struct sequence *seq = sequence_by_id(new_def->id); + assert(seq != NULL); + alter->old_def = seq->def; + alter->new_def = new_def; + } + + def_guard.is_active = false; + + trigger_create(&alter->on_commit, + on_commit_dd_sequence, alter, NULL); + txn_on_commit(txn, &alter->on_commit); + trigger_create(&alter->on_rollback, + on_rollback_dd_sequence, alter, NULL); + txn_on_rollback(txn, &alter->on_rollback); +} + +/** + * A trigger invoked on replace in space _sequence_data. + * Used to update a sequence value. + */ +static void +on_replace_dd_sequence_data(struct trigger * /* trigger */, void *event) +{ + struct txn *txn = (struct txn *) event; + struct txn_stmt *stmt = txn_current_stmt(txn); + struct tuple *old_tuple = stmt->old_tuple; + struct tuple *new_tuple = stmt->new_tuple; + + uint32_t id = tuple_field_u32_xc(old_tuple ?: new_tuple, + BOX_SEQUENCE_DATA_FIELD_ID); + struct sequence *seq = sequence_cache_find(id); + if (seq == NULL) + diag_raise(); + if (new_tuple != NULL) { /* INSERT, UPDATE */ + int64_t value = tuple_field_i64_xc(new_tuple, + BOX_SEQUENCE_DATA_FIELD_VALUE); + sequence_set(seq, value); + } else { /* DELETE */ + sequence_reset(seq); + } +} + +/* }}} sequence */ + static void unlock_after_dd(struct trigger *trigger, void *event) { @@ -2618,6 +2789,14 @@ struct trigger on_replace_cluster = { RLIST_LINK_INITIALIZER, on_replace_dd_cluster, NULL, NULL }; +struct trigger on_replace_sequence = { + RLIST_LINK_INITIALIZER, on_replace_dd_sequence, NULL, NULL +}; + +struct trigger on_replace_sequence_data = { + RLIST_LINK_INITIALIZER, on_replace_dd_sequence_data, 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 be480763cd7aeb28ee5d2405e4b26ac1968dc2d7..4835a0c7c90ae50f3e03513c06a266d0df8f8eb1 100644 --- a/src/box/alter.h +++ b/src/box/alter.h @@ -40,6 +40,8 @@ extern struct trigger on_replace_user; extern struct trigger on_replace_func; 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_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 8ddf41ab6fcccb66284d44e85888a18277fe150f..1977f0c8a3cda0165b0e67ae23f4e520907eefaf 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 0a416234727cfc251f25f0079ba3187c57c246f4..fb145d773a30e05e0ee742da753e4baf14c101e6 100644 --- a/src/box/box.cc +++ b/src/box/box.cc @@ -69,6 +69,7 @@ #include "systemd.h" #include "call.h" #include "func.h" +#include "sequence.h" static char status[64] = "unknown"; @@ -941,6 +942,89 @@ box_truncate(uint32_t space_id) } } +/** Update a record in _sequence_data space. */ +static int +sequence_data_update(uint32_t seq_id, int64_t value) +{ + size_t tuple_buf_size = (mp_sizeof_array(2) + + 2 * mp_sizeof_uint(UINT64_MAX)); + char *tuple_buf = (char *) region_alloc(&fiber()->gc, tuple_buf_size); + if (tuple_buf == NULL) { + diag_set(OutOfMemory, tuple_buf_size, "region", "tuple"); + return -1; + } + char *tuple_buf_end = tuple_buf; + tuple_buf_end = mp_encode_array(tuple_buf_end, 2); + tuple_buf_end = mp_encode_uint(tuple_buf_end, seq_id); + tuple_buf_end = (value < 0 ? + mp_encode_int(tuple_buf_end, value) : + mp_encode_uint(tuple_buf_end, value)); + assert(tuple_buf_end < tuple_buf + tuple_buf_size); + return box_replace(BOX_SEQUENCE_DATA_ID, + tuple_buf, tuple_buf_end, NULL); +} + +/** Delete a record from _sequence_data space. */ +static int +sequence_data_delete(uint32_t seq_id) +{ + size_t key_buf_size = mp_sizeof_array(1) + mp_sizeof_uint(UINT64_MAX); + char *key_buf = (char *) region_alloc(&fiber()->gc, key_buf_size); + if (key_buf == NULL) { + diag_set(OutOfMemory, key_buf_size, "region", "key"); + return -1; + } + char *key_buf_end = key_buf; + key_buf_end = mp_encode_array(key_buf_end, 1); + key_buf_end = mp_encode_uint(key_buf_end, seq_id); + assert(key_buf_end < key_buf + key_buf_size); + return box_delete(BOX_SEQUENCE_DATA_ID, 0, + key_buf, key_buf_end, NULL); +} + +int +box_sequence_next(uint32_t seq_id, int64_t *result) +{ + struct sequence *seq = sequence_cache_find(seq_id); + if (seq == NULL) + return -1; + int64_t value; + if (sequence_next(seq, &value) != 0) + return -1; + if (sequence_data_update(seq_id, value) != 0) + return -1; + *result = value; + return 0; +} +int +box_sequence_get(uint32_t seq_id, int64_t *result) +{ + struct sequence *seq = sequence_cache_find(seq_id); + if (seq == NULL) + return -1; + return sequence_get(seq, result); +} + +int +box_sequence_set(uint32_t seq_id, int64_t value) +{ + struct sequence *seq = sequence_cache_find(seq_id); + if (seq == NULL) + return -1; + sequence_set(seq, value); + return sequence_data_update(seq_id, value); +} + +int +box_sequence_reset(uint32_t seq_id) +{ + struct sequence *seq = sequence_cache_find(seq_id); + if (seq == NULL) + return -1; + sequence_reset(seq); + return sequence_data_delete(seq_id); +} + static inline void box_register_replica(uint32_t id, const struct tt_uuid *uuid) { diff --git a/src/box/box.h b/src/box/box.h index 3a9231de97c434ea465999f6809d1ed09196861c..460ac7fad8d56cec1a272dab44496518c63f6ba8 100644 --- a/src/box/box.h +++ b/src/box/box.h @@ -314,6 +314,52 @@ box_upsert(uint32_t space_id, uint32_t index_id, const char *tuple, API_EXPORT int box_truncate(uint32_t space_id); +/** + * Advance a sequence. + * + * \param seq_id sequence identifier + * \param[out] result pointer to a variable where the next sequence + * value will be stored on success + * \retval -1 on error (check box_error_last()) + * \retval 0 on success + */ +API_EXPORT int +box_sequence_next(uint32_t seq_id, int64_t *result); + +/** + * Get the last value returned by a sequence. + * + * \param seq_id sequence identifier + * \paramp[out] result pointer to a variable where the last sequence + * value will be stored on success + * \retval -1 on error (check box_error_last()) + * \retval 0 on success + */ +API_EXPORT int +box_sequence_get(uint32_t seq_id, int64_t *result); + +/** + * Set a sequence value. + * + * \param seq_id sequence identifier + * \param value new sequence value; on success the next call to + * box_sequence_next() will return the value following \a value + * \retval -1 on error (check box_error_last()) + * \retval 0 on success + */ +API_EXPORT int +box_sequence_set(uint32_t seq_id, int64_t value); + +/** + * Reset a sequence. + * + * \param seq_id sequence identifier + * \retval -1 on error (check box_error_last()) + * \retval 0 on success + */ +API_EXPORT int +box_sequence_reset(uint32_t seq_id); + /** \endcond public */ /** diff --git a/src/box/errcode.h b/src/box/errcode.h index 90f15015b35fc47eda7566ee9bc49daefb6a04df..ae67b5277fa6e3a64bda4c3bcf68d37373d83af1 100644 --- a/src/box/errcode.h +++ b/src/box/errcode.h @@ -194,6 +194,13 @@ struct errcode_record { /*139 */_(ER_VINYL_MAX_TUPLE_SIZE, "Failed to allocate %u bytes for tuple: tuple is too large. Check 'vinyl_max_tuple_size' configuration option.") \ /*140 */_(ER_WRONG_DD_VERSION, "Wrong _schema version: expected 'major.minor[.patch]'") \ /*141 */_(ER_WRONG_SPACE_FORMAT, "Wrong space format (field %u): %s") \ + /*142 */_(ER_CREATE_SEQUENCE, "Failed to create sequence '%s': %s") \ + /*143 */_(ER_ALTER_SEQUENCE, "Can't modify sequence '%s': %s") \ + /*144 */_(ER_DROP_SEQUENCE, "Can't drop sequence '%s': %s") \ + /*145 */_(ER_NO_SUCH_SEQUENCE, "Sequence '%s' does not exist") \ + /*146 */_(ER_SEQUENCE_EXISTS, "Sequence '%s' already exists") \ + /*147 */_(ER_SEQUENCE_OVERFLOW, "Sequence '%s' has overflowed") \ + /*148 */_(ER_SEQUENCE_NOT_STARTED, "Sequence '%s' is not started") \ /* * !IMPORTANT! Please follow instructions at start of the file diff --git a/src/box/lua/init.c b/src/box/lua/init.c index 8dc03ddedd02b8825868f21e7ca3aa442f5505f2..a6a23696fb390103de7caa4f720fc46a0c459c29 100644 --- a/src/box/lua/init.c +++ b/src/box/lua/init.c @@ -48,6 +48,7 @@ #include "box/lua/slab.h" #include "box/lua/index.h" #include "box/lua/space.h" +#include "box/lua/sequence.h" #include "box/lua/misc.h" #include "box/lua/stat.h" #include "box/lua/info.h" @@ -236,6 +237,7 @@ box_lua_init(struct lua_State *L) box_lua_slab_init(L); box_lua_index_init(L); box_lua_space_init(L); + box_lua_sequence_init(L); box_lua_misc_init(L); box_lua_info_init(L); box_lua_stat_init(L); diff --git a/src/box/lua/load_cfg.lua b/src/box/lua/load_cfg.lua index 9f5f1749da20139ce1d78c50699cd49c5dd2aa69..60549939d4f72cc985bd4a9fe5fa2bc1d664024a 100644 --- a/src/box/lua/load_cfg.lua +++ b/src/box/lua/load_cfg.lua @@ -347,6 +347,7 @@ setmetatable(box, { }) local function load_cfg(cfg) + box.internal.schema.init() cfg = upgrade_cfg(cfg, translate_cfg) cfg = prepare_cfg(cfg, default_cfg, template_cfg, modify_cfg) apply_default_cfg(cfg, default_cfg); diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua index 61dba31f87389c3d50858cac0ec1626d0f701ed1..8fb898dea4302fa2a6a33f688567d3f662fac5df 100644 --- a/src/box/lua/schema.lua +++ b/src/box/lua/schema.lua @@ -19,6 +19,9 @@ local tuple_bless = box.tuple.bless local is_tuple = box.tuple.is assert(tuple_encode ~= nil and tuple_bless ~= nil and is_tuple ~= nil) +local INT64_MIN = tonumber64('-9223372036854775808') +local INT64_MAX = tonumber64('9223372036854775807') + ffi.cdef[[ struct space *space_by_id(uint32_t id); extern uint32_t box_schema_version(); @@ -142,6 +145,31 @@ local function user_resolve(name_or_id) end end +local function sequence_resolve(name_or_id) + local _sequence = box.space[box.schema.SEQUENCE_ID] + local tuple + if type(name_or_id) == 'string' then + tuple = _sequence.index.name:get{name_or_id} + elseif type(name_or_id) ~= 'nil' then + tuple = _sequence:get{name_or_id} + end + if tuple ~= nil then + return tuple[1], tuple + else + return nil + end +end + +-- Same as type(), but returns 'number' if 'param' is +-- of type 'cdata' and represents a 64-bit integer. +local function param_type(param) + local t = type(param) + if t == 'cdata' and tonumber64(param) ~= nil then + t = 'number' + end + return t +end + --[[ @brief Common function to check table with parameters (like options) @param table - table with parameters @@ -180,7 +208,7 @@ local function check_param_table(table, template) -- any type is ok elseif (string.find(template[k], ',') == nil) then -- one type - if type(v) ~= template[k] then + if param_type(v) ~= template[k] then box.error(box.error.ILLEGAL_PARAMS, "options parameter '" .. k .. "' should be of type " .. template[k]) @@ -188,7 +216,7 @@ local function check_param_table(table, template) else local good_types = string.gsub(template[k], ' ', '') local haystack = ',' .. good_types .. ',' - local needle = ',' .. type(v) .. ',' + local needle = ',' .. param_type(v) .. ',' if (string.find(haystack, needle) == nil) then good_types = string.gsub(good_types, ',', ', ') box.error(box.error.ILLEGAL_PARAMS, @@ -205,7 +233,7 @@ end @example: check_param(user, 'user', 'string') --]] local function check_param(param, name, should_be_type) - if type(param) ~= should_be_type then + if param_type(param) ~= should_be_type then box.error(box.error.ILLEGAL_PARAMS, name .. " should be a " .. should_be_type) end @@ -1163,6 +1191,147 @@ function box.schema.space.bless(space) end end +local sequence_mt = {} +sequence_mt.__index = sequence_mt + +sequence_mt.next = function(self) + return internal.sequence.next(self.id) +end + +sequence_mt.get = function(self) + return internal.sequence.get(self.id) +end + +sequence_mt.set = function(self, value) + return internal.sequence.set(self.id, value) +end + +sequence_mt.reset = function(self) + return internal.sequence.reset(self.id) +end + +sequence_mt.alter = function(self, opts) + box.schema.sequence.alter(self.id, opts) +end + +sequence_mt.drop = function(self) + box.schema.sequence.drop(self.id) +end + +local function sequence_tuple_decode(seq, tuple) + seq.id, seq.uid, seq.name, seq.step, seq.min, seq.max, + seq.start, seq.cache, seq.cycle = tuple:unpack() +end + +local function sequence_new(tuple) + local seq = setmetatable({}, sequence_mt) + sequence_tuple_decode(seq, tuple) + return seq +end + +local function sequence_on_alter(old_tuple, new_tuple) + if old_tuple and not new_tuple then + local old_name = old_tuple[3] + box.sequence[old_name] = nil + elseif not old_tuple and new_tuple then + local seq = sequence_new(new_tuple) + box.sequence[seq.name] = seq + else + local old_name = old_tuple[3] + local seq = box.sequence[old_name] + if not seq then + seq = sequence_new(seq, new_tuple) + else + sequence_tuple_decode(seq, new_tuple) + end + box.sequence[old_name] = nil + box.sequence[seq.name] = seq + end +end + +box.sequence = {} +local function box_sequence_init() + -- Install a trigger that will update Lua objects on + -- _sequence space modifications. + internal.sequence.on_alter(sequence_on_alter) +end + +local sequence_options = { + step = 'number', + min = 'number', + max = 'number', + start = 'number', + cache = 'number', + cycle = 'boolean', +} + +local create_sequence_options = table.deepcopy(sequence_options) +create_sequence_options.if_not_exists = 'boolean' + +local alter_sequence_options = table.deepcopy(sequence_options) +alter_sequence_options.name = 'string' + +box.schema.sequence = {} +box.schema.sequence.create = function(name, opts) + opts = opts or {} + check_param(name, 'name', 'string') + check_param_table(opts, create_sequence_options) + local ascending = not opts.step or opts.step > 0 + local options_defaults = { + step = 1, + min = ascending and 1 or INT64_MIN, + max = ascending and INT64_MAX or -1, + start = ascending and (opts.min or 1) or (opts.max or -1), + cache = 0, + cycle = false, + } + opts = update_param_table(opts, options_defaults) + local id = sequence_resolve(name) + if id ~= nil then + if not opts.if_not_exists then + box.error(box.error.SEQUENCE_EXISTS, name) + end + return box.sequence[name], 'not created' + end + local _sequence = box.space[box.schema.SEQUENCE_ID] + _sequence:auto_increment{session.uid(), name, opts.step, opts.min, + opts.max, opts.start, opts.cache, opts.cycle} + return box.sequence[name] +end + +box.schema.sequence.alter = function(name, opts) + check_param_table(opts, alter_sequence_options) + local id, tuple = sequence_resolve(name) + if id == nil then + box.error(box.error.NO_SUCH_SEQUENCE, name) + end + if opts == nil then + return + end + local seq = {} + sequence_tuple_decode(seq, tuple) + opts = update_param_table(opts, seq) + local _sequence = box.space[box.schema.SEQUENCE_ID] + _sequence:replace{seq.id, seq.uid, opts.name, opts.step, opts.min, + opts.max, opts.start, opts.cache, opts.cycle} +end + +box.schema.sequence.drop = function(name, opts) + opts = opts or {} + check_param_table(opts, {if_exists = 'boolean'}) + local id = sequence_resolve(name) + if id == nil then + if not opts.if_exists then + box.error(box.error.NO_SUCH_SEQUENCE, name) + end + return + end + local _sequence = box.space[box.schema.SEQUENCE_ID] + local _sequence_data = box.space[box.schema.SEQUENCE_DATA_ID] + _sequence_data:delete{id} + _sequence:delete{id} +end + local function privilege_resolve(privilege) local numeric = 0 if type(privilege) == 'string' then @@ -1491,6 +1660,10 @@ local function drop(uid, opts) for k, tuple in pairs(grants) do revoke(tuple[2], tuple[2], uid) end + local sequences = box.space[box.schema.SEQUENCE_ID].index.owner:select{uid} + for k, tuple in pairs(sequences) do + box.schema.sequence.drop(tuple[1]) + end box.space[box.schema.USER_ID]:delete{uid} end @@ -1651,3 +1824,8 @@ local function box_space_mt(tab) end setmetatable(box.space, { __serialize = box_space_mt }) + +box.internal.schema = {} +box.internal.schema.init = function() + box_sequence_init() +end diff --git a/src/box/lua/sequence.c b/src/box/lua/sequence.c new file mode 100644 index 0000000000000000000000000000000000000000..63d3471773e745de2966a7d7827552b944f41aa2 --- /dev/null +++ b/src/box/lua/sequence.c @@ -0,0 +1,119 @@ +/* + * Copyright 2010-2017, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include "box/lua/sequence.h" +#include "box/lua/tuple.h" +#include "lua/utils.h" +#include "lua/trigger.h" + +#include "diag.h" +#include "box/box.h" +#include "box/schema.h" +#include "box/txn.h" + +static int +lbox_sequence_next(struct lua_State *L) +{ + uint32_t seq_id = luaL_checkinteger(L, 1); + int64_t result; + if (box_sequence_next(seq_id, &result) != 0) + luaT_error(L); + luaL_pushint64(L, result); + return 1; +} + +static int +lbox_sequence_get(struct lua_State *L) +{ + uint32_t seq_id = luaL_checkinteger(L, 1); + int64_t result; + if (box_sequence_get(seq_id, &result) != 0) + luaT_error(L); + luaL_pushint64(L, result); + return 1; +} + +static int +lbox_sequence_set(struct lua_State *L) +{ + uint32_t seq_id = luaL_checkinteger(L, 1); + int64_t value = luaL_checkint64(L, 2); + if (box_sequence_set(seq_id, value) != 0) + luaT_error(L); + return 0; +} + +static int +lbox_sequence_reset(struct lua_State *L) +{ + uint32_t seq_id = luaL_checkinteger(L, 1); + if (box_sequence_reset(seq_id) != 0) + luaT_error(L); + return 0; +} + +static int +lbox_sequence_push_on_alter_event(struct lua_State *L, void *event) +{ + struct txn_stmt *stmt = (struct txn_stmt *) event; + if (stmt->old_tuple) { + luaT_pushtuple(L, stmt->old_tuple); + } else { + lua_pushnil(L); + } + if (stmt->new_tuple) { + luaT_pushtuple(L, stmt->new_tuple); + } else { + lua_pushnil(L); + } + return 2; +} + +static int +lbox_sequence_on_alter(struct lua_State *L) +{ + return lbox_trigger_reset(L, 2, &on_alter_sequence, + lbox_sequence_push_on_alter_event); +} + +void +box_lua_sequence_init(struct lua_State *L) +{ + static const struct luaL_Reg sequence_internal_lib[] = { + {"next", lbox_sequence_next}, + {"get", lbox_sequence_get}, + {"set", lbox_sequence_set}, + {"reset", lbox_sequence_reset}, + {"on_alter", lbox_sequence_on_alter}, + {NULL, NULL} + }; + luaL_register(L, "box.internal.sequence", sequence_internal_lib); + lua_pop(L, 1); +} diff --git a/src/box/lua/sequence.h b/src/box/lua/sequence.h new file mode 100644 index 0000000000000000000000000000000000000000..14c41ffc1279cb3e94fcb6df3b7dabd0438553fd --- /dev/null +++ b/src/box/lua/sequence.h @@ -0,0 +1,47 @@ +#ifndef INCLUDES_TARANTOOL_MOD_BOX_LUA_SEQUENCE_H +#define INCLUDES_TARANTOOL_MOD_BOX_LUA_SEQUENCE_H +/* + * Copyright 2010-2017, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(__cplusplus) +extern "C" { +#endif /* defined(__cplusplus) */ + +struct lua_State; + +void +box_lua_sequence_init(struct lua_State *L); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif /* defined(__cplusplus) */ + +#endif /* INCLUDES_TARANTOOL_MOD_BOX_LUA_SEQUENCE_H */ diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc index 44893c8be37e87552f6db4a49092d3f7ca2b06d1..ecaa6bccb6dca010c3e2aac4aec85dea5e8db6f2 100644 --- a/src/box/lua/space.cc +++ b/src/box/lua/space.cc @@ -355,6 +355,10 @@ box_lua_space_init(struct lua_State *L) lua_setfield(L, -2, "CLUSTER_ID"); lua_pushnumber(L, BOX_TRUNCATE_ID); lua_setfield(L, -2, "TRUNCATE_ID"); + lua_pushnumber(L, BOX_SEQUENCE_ID); + lua_setfield(L, -2, "SEQUENCE_ID"); + lua_pushnumber(L, BOX_SEQUENCE_DATA_ID); + lua_setfield(L, -2, "SEQUENCE_DATA_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 1328a1e049517282b9e4cd65143c935144d1f43a..fb5d1dd06c0e0c704d9d4817a22797fec6333267 100644 --- a/src/box/lua/upgrade.lua +++ b/src/box/lua/upgrade.lua @@ -86,6 +86,8 @@ local function erase() truncate(box.space._user) truncate(box.space._func) truncate(box.space._priv) + truncate(box.space._sequence_data) + truncate(box.space._sequence) truncate(box.space._truncate) --truncate(box.space._schema) box.space._schema:delete('version') @@ -837,8 +839,41 @@ end -- Tarantool 1.7.6 -------------------------------------------------------------------------------- +local function create_sequence_space() + local _space = box.space[box.schema.SPACE_ID] + 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 MAP = setmap({}) + + log.info("create space _sequence") + _space:insert{_sequence.id, ADMIN, '_sequence', 'memtx', 0, MAP, + {{name = 'id', type = 'unsigned'}, + {name = 'owner', type = 'unsigned'}, + {name = 'name', type = 'string'}, + {name = 'step', type = 'integer'}, + {name = 'min', type = 'integer'}, + {name = 'max', type = 'integer'}, + {name = 'start', type = 'integer'}, + {name = 'cache', type = 'integer'}, + {name = 'cycle', type = 'boolean'}}} + log.info("create index _sequence:primary") + _index:insert{_sequence.id, 0, 'primary', 'tree', {unique = true}, {{0, 'unsigned'}}} + log.info("create index _sequence:owner") + _index:insert{_sequence.id, 1, 'owner', 'tree', {unique = false}, {{1, 'unsigned'}}} + log.info("create index _sequence:name") + _index:insert{_sequence.id, 2, 'name', 'tree', {unique = true}, {{2, 'string'}}} + + log.info("create space _sequence_data") + _space:insert{_sequence_data.id, ADMIN, '_sequence_data', 'memtx', 0, MAP, + {{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'}}} +end + local function upgrade_to_1_7_6() --- Trigger space format checking by updating version in _schema. + create_sequence_space() + -- Trigger space format checking by updating version in _schema. end -------------------------------------------------------------------------------- diff --git a/src/box/schema.cc b/src/box/schema.cc index e5c83f62cb3c242319303db6c2b88b83433fd583..c4837dead583d6fed730f3d9fa9c56cc00139a02 100644 --- a/src/box/schema.cc +++ b/src/box/schema.cc @@ -33,6 +33,7 @@ #include "engine.h" #include "memtx_index.h" #include "func.h" +#include "sequence.h" #include "tuple.h" #include "assoc.h" #include "alter.h" @@ -58,10 +59,12 @@ static struct mh_i32ptr_t *spaces; static struct mh_i32ptr_t *funcs; static struct mh_strnptr_t *funcs_by_name; +static struct mh_i32ptr_t *sequences; uint32_t schema_version = 0; uint32_t dd_version_id = version_id(1, 6, 4); struct rlist on_alter_space = RLIST_HEAD_INITIALIZER(on_alter_space); +struct rlist on_alter_sequence = RLIST_HEAD_INITIALIZER(on_alter_sequence); /** * Lock of scheme modification @@ -251,6 +254,7 @@ schema_init() spaces = mh_i32ptr_new(); funcs = mh_i32ptr_new(); funcs_by_name = mh_strnptr_new(); + sequences = mh_i32ptr_new(); /* * Create surrogate space objects for the mandatory system * spaces (the primal eggs from which we get all the @@ -282,6 +286,14 @@ schema_init() sc_space_new(BOX_TRUNCATE_ID, "_truncate", key_def, &on_replace_truncate, &on_stmt_begin_truncate); + /* _sequence - definition of all sequence objects. */ + sc_space_new(BOX_SEQUENCE_ID, "_sequence", key_def, + &on_replace_sequence, NULL); + + /* _sequence_data - current sequence value. */ + sc_space_new(BOX_SEQUENCE_DATA_ID, "_sequence_data", key_def, + &on_replace_sequence_data, NULL); + /* _user - all existing users */ sc_space_new(BOX_USER_ID, "_user", key_def, &on_replace_user, NULL); @@ -335,6 +347,14 @@ schema_free(void) func_cache_delete(func->def->fid); } mh_i32ptr_delete(funcs); + while (mh_size(sequences) > 0) { + mh_int_t i = mh_first(sequences); + + struct sequence *seq = ((struct sequence *) + mh_i32ptr_node(sequences, i)->val); + sequence_cache_delete(seq->def->id); + } + mh_i32ptr_delete(sequences); } void @@ -425,3 +445,57 @@ schema_find_grants(const char *type, uint32_t id) index->initIterator(it, ITER_EQ, key, 2); return it->next(it); } + +struct sequence * +sequence_by_id(uint32_t id) +{ + mh_int_t k = mh_i32ptr_find(sequences, id, NULL); + if (k == mh_end(sequences)) + return NULL; + return (struct sequence *) mh_i32ptr_node(sequences, k)->val; +} + +struct sequence * +sequence_cache_find(uint32_t id) +{ + struct sequence *seq = sequence_by_id(id); + if (seq == NULL) + tnt_raise(ClientError, ER_NO_SUCH_SEQUENCE, int2str(id)); + return seq; +} + +void +sequence_cache_replace(struct sequence_def *def) +{ + struct sequence *seq = sequence_by_id(def->id); + if (seq == NULL) { + /* Create a new sequence. */ + seq = (struct sequence *) calloc(1, sizeof(*seq)); + if (seq == NULL) + goto error; + struct mh_i32ptr_node_t node = { def->id, seq }; + if (mh_i32ptr_put(sequences, &node, NULL, NULL) == + mh_end(sequences)) + goto error; + } else { + /* Update an existing sequence. */ + free(seq->def); + } + seq->def = def; + return; +error: + panic_syserror("Out of memory for the data " + "dictionary cache (sequence)."); +} + +void +sequence_cache_delete(uint32_t id) +{ + struct sequence *seq = sequence_by_id(id); + if (seq != NULL) { + mh_i32ptr_del(sequences, seq->def->id, NULL); + free(seq->def); + TRASH(seq); + free(seq); + } +} diff --git a/src/box/schema.h b/src/box/schema.h index a631de8d434ae52b23cc1e2c122fb4338c25e16c..6e35961575b46f42bf4a27c2eb44696dd89e1c13 100644 --- a/src/box/schema.h +++ b/src/box/schema.h @@ -154,6 +154,34 @@ func_by_name(const char *name, uint32_t name_len); bool schema_find_grants(const char *type, uint32_t id); +/** + * Find a sequence by id. Return NULL if the sequence was + * not found. + */ +struct sequence * +sequence_by_id(uint32_t id); + +/** + * A wrapper around sequence_by_id() that raises an exception + * if the sequence was not found in the cache. + */ +struct sequence * +sequence_cache_find(uint32_t id); + +/** + * Insert a new sequence object into the cache or update + * an existing one if there's already a sequence with + * the given id in the cache. + */ +void +sequence_cache_replace(struct sequence_def *def); + +/** Delete a sequence from the sequence cache. */ +void +sequence_cache_delete(uint32_t id); + +#endif /* defined(__cplusplus) */ + /** * Triggers fired after committing a change in space definition. * The space is passed to the trigger callback in the event @@ -162,6 +190,10 @@ schema_find_grants(const char *type, uint32_t id); */ extern struct rlist on_alter_space; -#endif /* defined(__cplusplus) */ +/** + * Triggers fired after committing a change in sequence definition. + * It is passed the txn statement that altered the sequence. + */ +extern struct rlist on_alter_sequence; #endif /* INCLUDES_TARANTOOL_BOX_SCHEMA_H */ diff --git a/src/box/schema_def.h b/src/box/schema_def.h index 591133762a28072de3ec93f631ca4769e5ffecb1..046dd5306fcb3e04b395ae7165c45ef9d7c3cf93 100644 --- a/src/box/schema_def.h +++ b/src/box/schema_def.h @@ -72,6 +72,10 @@ enum { BOX_SPACE_ID = 280, /** Space id of _vspace view. */ BOX_VSPACE_ID = 281, + /** Space id of _sequence. */ + BOX_SEQUENCE_ID = 284, + /** Space id of _sequence_data. */ + BOX_SEQUENCE_DATA_ID = 285, /** Space id of _index. */ BOX_INDEX_ID = 288, /** Space id of _vindex view. */ @@ -166,6 +170,25 @@ enum { BOX_TRUNCATE_FIELD_COUNT = 1, }; +/** _sequence fields. */ +enum { + BOX_SEQUENCE_FIELD_ID = 0, + BOX_SEQUENCE_FIELD_UID = 1, + BOX_SEQUENCE_FIELD_NAME = 2, + BOX_SEQUENCE_FIELD_STEP = 3, + BOX_SEQUENCE_FIELD_MIN = 4, + BOX_SEQUENCE_FIELD_MAX = 5, + BOX_SEQUENCE_FIELD_START = 6, + BOX_SEQUENCE_FIELD_CACHE = 7, + BOX_SEQUENCE_FIELD_CYCLE = 8, +}; + +/** _sequence_data fields. */ +enum { + BOX_SEQUENCE_DATA_FIELD_ID = 0, + BOX_SEQUENCE_DATA_FIELD_VALUE = 1, +}; + /* * Different objects which can be subject to access * control. diff --git a/src/box/sequence.c b/src/box/sequence.c new file mode 100644 index 0000000000000000000000000000000000000000..b43bb9217c85f72b36af684403ff9e75a82bd8fa --- /dev/null +++ b/src/box/sequence.c @@ -0,0 +1,95 @@ +/* + * Copyright 2010-2017, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include "sequence.h" + +#include <stdbool.h> +#include <stdint.h> + +#include "diag.h" +#include "error.h" +#include "errcode.h" + +int +sequence_next(struct sequence *seq, int64_t *result) +{ + int64_t value; + struct sequence_def *def = seq->def; + if (!seq->is_started) { + value = def->start; + seq->is_started = true; + goto done; + } + value = seq->value; + if (def->step > 0) { + if (value < def->min) { + value = def->min; + goto done; + } + if (value >= 0 && def->step > INT64_MAX - value) + goto overflow; + value += def->step; + if (value > def->max) + goto overflow; + } else { + assert(def->step < 0); + if (value > def->max) { + value = def->max; + goto done; + } + if (value < 0 && def->step < INT64_MIN - value) + goto overflow; + value += def->step; + if (value < def->min) + goto overflow; + } +done: + assert(value >= def->min && value <= def->max); + *result = seq->value = value; + return 0; +overflow: + if (!def->cycle) { + diag_set(ClientError, ER_SEQUENCE_OVERFLOW, def->name); + return -1; + } + value = def->step > 0 ? def->min : def->max; + goto done; +} + +int +sequence_get(struct sequence *seq, int64_t *result) +{ + if (!seq->is_started) { + diag_set(ClientError, ER_SEQUENCE_NOT_STARTED, seq->def->name); + return -1; + } + *result = seq->value; + return 0; +} diff --git a/src/box/sequence.h b/src/box/sequence.h new file mode 100644 index 0000000000000000000000000000000000000000..6ea016265a22e1ff3a5789b17bcafcfaf4df0626 --- /dev/null +++ b/src/box/sequence.h @@ -0,0 +1,128 @@ +#ifndef INCLUDES_TARANTOOL_BOX_SEQUENCE_H +#define INCLUDES_TARANTOOL_BOX_SEQUENCE_H +/* + * Copyright 2010-2017, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <assert.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +#if defined(__cplusplus) +extern "C" { +#endif /* defined(__cplusplus) */ + +/** Sequence metadata. */ +struct sequence_def { + /** Sequence id. */ + uint32_t id; + /** Owner of the sequence. */ + uint32_t uid; + /** + * The value added to the sequence at each step. + * If it is positive, the sequence is ascending, + * otherwise it is descending. + */ + int64_t step; + /** Min sequence value. */ + int64_t min; + /** Max sequence value. */ + int64_t max; + /** Initial sequence value. */ + int64_t start; + /** Number of values to preallocate. Not implemented yet. */ + int64_t cache; + /** + * If this flag is set, the sequence will wrap + * upon reaching min or max value by a descending + * or ascending sequence respectively. + */ + bool cycle; + /** Sequence name. */ + char name[0]; +}; + +/** Sequence object. */ +struct sequence { + /** Sequence definition. */ + struct sequence_def *def; + /** Last value returned by the sequence. */ + int64_t value; + /** True if the sequence was started. */ + bool is_started; +}; + +static inline size_t +sequence_def_sizeof(uint32_t name_len) +{ + return sizeof(struct sequence_def) + name_len + 1; +} + +/** Reset a sequence. */ +static inline void +sequence_reset(struct sequence *seq) +{ + seq->is_started = false; +} + +/** Set a sequence value. */ +static inline void +sequence_set(struct sequence *seq, int64_t value) +{ + seq->value = value; + seq->is_started = true; +} + +/** + * Advance a sequence. + * + * On success, return 0 and assign the next sequence to + * @result. If the sequence isn't cyclic and has reached + * its limit, return -1 and set diag. + */ +int +sequence_next(struct sequence *seq, int64_t *result); + +/** + * Get the last value returned by a sequence. + * + * Return 0 and assign the last sequence value to @result + * if the sequence was started. If it was not, return -1 + * and raise ER_SEQUENCE_NOT_STARTED error. + */ +int +sequence_get(struct sequence *seq, int64_t *result); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif /* defined(__cplusplus) */ + +#endif /* INCLUDES_TARANTOOL_BOX_SEQUENCE_H */ diff --git a/src/box/tuple.h b/src/box/tuple.h index 2a5632ab552b4444ee0ff1f38ee944bc2c6ef749..366137a08a0773fdf51541adb3febc6d9e5080a5 100644 --- a/src/box/tuple.h +++ b/src/box/tuple.h @@ -663,6 +663,52 @@ tuple_field_with_type(const struct tuple *tuple, uint32_t fieldno, return field; } +/** + * A convenience shortcut for data dictionary - get a tuple field + * as bool. + */ +static inline int +tuple_field_bool(const struct tuple *tuple, uint32_t fieldno, bool *out) +{ + const char *field = tuple_field_with_type(tuple, fieldno, MP_BOOL); + if (field == NULL) + return -1; + *out = mp_decode_bool(&field); + return 0; +} + +/** + * A convenience shortcut for data dictionary - get a tuple field + * as int64_t. + */ +static inline int +tuple_field_i64(const struct tuple *tuple, uint32_t fieldno, int64_t *out) +{ + const char *field = tuple_field(tuple, fieldno); + if (field == NULL) { + diag_set(ClientError, ER_NO_SUCH_FIELD, fieldno); + return -1; + } + uint64_t val; + switch (mp_typeof(*field)) { + case MP_INT: + *out = mp_decode_int(&field); + break; + case MP_UINT: + val = mp_decode_uint(&field); + if (val <= INT64_MAX) { + *out = val; + break; + } + FALLTHROUGH; + default: + diag_set(ClientError, ER_FIELD_TYPE, fieldno + TUPLE_INDEX_BASE, + field_type_strs[FIELD_TYPE_INTEGER]); + return -1; + } + return 0; +} + /** * A convenience shortcut for data dictionary - get a tuple field * as uint64_t. @@ -860,6 +906,26 @@ tuple_field_with_type_xc(const struct tuple *tuple, uint32_t fieldno, return out; } +/* @copydoc tuple_field_bool() */ +static inline bool +tuple_field_bool_xc(const struct tuple *tuple, uint32_t fieldno) +{ + bool out; + if (tuple_field_bool(tuple, fieldno, &out) != 0) + diag_raise(); + return out; +} + +/* @copydoc tuple_field_i64() */ +static inline int64_t +tuple_field_i64_xc(const struct tuple *tuple, uint32_t fieldno) +{ + int64_t out; + if (tuple_field_i64(tuple, fieldno, &out) != 0) + diag_raise(); + return out; +} + /* @copydoc tuple_field_u64() */ static inline uint64_t tuple_field_u64_xc(const struct tuple *tuple, uint32_t fieldno) diff --git a/test/app-tap/tarantoolctl.test.lua b/test/app-tap/tarantoolctl.test.lua index 1ce4964946a95a7d0cdb8f78da1bc489ca9e59a4..c1982bed663bf24f01575d5b636f5d707f89a4ea 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", 13) - check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 32) + check_ctlcat_snap(test_i, dir, "--space=280", "---\n", 15) + check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 36) end) end) diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result index 2a40bb60cb2a4a2fdec6b5613eabc5962a1c0aaa..d6ef7d692b8d707060d06e254670c48d43779790 100644 --- a/test/box-py/bootstrap.result +++ b/test/box-py/bootstrap.result @@ -22,6 +22,13 @@ box.space._space:select{} 'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'engine', 'type': 'string'}, {'name': 'field_count', 'type': 'unsigned'}, {'name': 'flags', 'type': 'map'}, {'name': 'format', 'type': 'array'}]] + - [284, 1, '_sequence', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'owner', + 'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'step', + 'type': 'integer'}, {'name': 'min', 'type': 'integer'}, {'name': 'max', 'type': 'integer'}, + {'name': 'start', 'type': 'integer'}, {'name': 'cache', 'type': 'integer'}, + {'name': 'cycle', 'type': 'boolean'}]] + - [285, 1, '_sequence_data', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, + {'name': 'value', 'type': 'integer'}]] - [288, 1, '_index', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'iid', 'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'type', 'type': 'string'}, {'name': 'opts', 'type': 'map'}, {'name': 'parts', 'type': 'array'}]] @@ -60,6 +67,10 @@ box.space._index:select{} - [281, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]] - [281, 1, 'owner', 'tree', {'unique': false}, [[1, 'unsigned']]] - [281, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]] + - [284, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]] + - [284, 1, 'owner', 'tree', {'unique': false}, [[1, 'unsigned']]] + - [284, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]] + - [285, 0, 'primary', 'hash', {'unique': true}, [[0, 'unsigned']]] - [288, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned'], [1, 'unsigned']]] - [288, 2, 'name', 'tree', {'unique': true}, [[0, 'unsigned'], [2, 'string']]] - [289, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned'], [1, 'unsigned']]] diff --git a/test/box/access_misc.result b/test/box/access_misc.result index 455ec1f9a43ecf6115699da61e68f266d1ed740a..a1080c5dae243499d8059cd70b11fbfdc871ada1 100644 --- a/test/box/access_misc.result +++ b/test/box/access_misc.result @@ -631,6 +631,13 @@ box.space._space:select() 'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'engine', 'type': 'string'}, {'name': 'field_count', 'type': 'unsigned'}, {'name': 'flags', 'type': 'map'}, {'name': 'format', 'type': 'array'}]] + - [284, 1, '_sequence', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'owner', + 'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'step', + 'type': 'integer'}, {'name': 'min', 'type': 'integer'}, {'name': 'max', 'type': 'integer'}, + {'name': 'start', 'type': 'integer'}, {'name': 'cache', 'type': 'integer'}, + {'name': 'cycle', 'type': 'boolean'}]] + - [285, 1, '_sequence_data', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, + {'name': 'value', 'type': 'integer'}]] - [288, 1, '_index', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'iid', 'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'type', 'type': 'string'}, {'name': 'opts', 'type': 'map'}, {'name': 'parts', 'type': 'array'}]] diff --git a/test/box/access_sysview.result b/test/box/access_sysview.result index b7328e61e73b4b2b44fc56a9d921cd05e3291d22..faf3f1a5dab828f197ca6e6d6aa854c2a872dd74 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{} --- -- 14 +- 16 ... #box.space._vindex:select{} --- -- 33 +- 37 ... #box.space._vuser:select{} --- @@ -262,7 +262,7 @@ box.session.su('guest') ... #box.space._vindex:select{} --- -- 33 +- 37 ... #box.space._vuser:select{} --- diff --git a/test/box/alter.result b/test/box/alter.result index 9401122600b0b28d15acd079e972c2ef29d36904..3bf1c1a10dde0ea5fb7ab12871880ff76ea867ba 100644 --- a/test/box/alter.result +++ b/test/box/alter.result @@ -179,6 +179,10 @@ _index:select{} - [281, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]] - [281, 1, 'owner', 'tree', {'unique': false}, [[1, 'unsigned']]] - [281, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]] + - [284, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]] + - [284, 1, 'owner', 'tree', {'unique': false}, [[1, 'unsigned']]] + - [284, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]] + - [285, 0, 'primary', 'hash', {'unique': true}, [[0, 'unsigned']]] - [288, 0, 'primary', 'tree', 1, 2, 0, 'unsigned', 1, 'unsigned'] - [288, 2, 'name', 'tree', {'unique': true}, [[0, 'unsigned'], [2, 'string']]] - [289, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned'], [1, 'unsigned']]] diff --git a/test/box/misc.result b/test/box/misc.result index 6b362c0dca5b54e8b73aef4bf6ae93da93377b4d..f904fa43a2528326afc9e8b76ff1bf9cd0007d0b 100644 --- a/test/box/misc.result +++ b/test/box/misc.result @@ -70,6 +70,7 @@ t - runtime - savepoint - schema + - sequence - session - slab - snapshot @@ -306,12 +307,14 @@ t; - - 'box.error.UNKNOWN_REPLICA : 62' - 'box.error.WRONG_INDEX_RECORD : 106' - 'box.error.NO_SUCH_TRIGGER : 34' + - 'box.error.SEQUENCE_EXISTS : 146' - 'box.error.CHECKPOINT_IN_PROGRESS : 120' - 'box.error.FIELD_TYPE : 23' - 'box.error.WRONG_SPACE_FORMAT : 141' - 'box.error.UNKNOWN_UPDATE_OP : 28' - 'box.error.CURSOR_NO_TRANSACTION : 80' - 'box.error.TUPLE_REF_OVERFLOW : 86' + - 'box.error.ALTER_SEQUENCE : 143' - 'box.error.INVALID_XLOG_NAME : 75' - 'box.error.SAVEPOINT_EMPTY_TX : 60' - 'box.error.NO_SUCH_FUNCTION : 51' @@ -378,11 +381,16 @@ t; - 'box.error.IDENTIFIER : 70' - 'box.error.NO_SUCH_ENGINE : 57' - 'box.error.COMMIT_IN_SUB_STMT : 122' + - 'box.error.injection : table: <address> + - 'box.error.SEQUENCE_NOT_STARTED : 148' - 'box.error.LAST_DROP : 15' + - 'box.error.SEQUENCE_OVERFLOW : 147' - 'box.error.DECOMPRESSION : 124' + - 'box.error.CREATE_SEQUENCE : 142' - 'box.error.CREATE_USER : 43' + - 'box.error.INJECTION : 8' - 'box.error.INSTANCE_UUID_MISMATCH : 66' - - 'box.error.injection : table: <address> + - 'box.error.FUNCTION_MAX : 54' - 'box.error.SYSTEM : 115' - 'box.error.KEY_PART_IS_TOO_LONG : 118' - 'box.error.TRUNCATE_SYSTEM_SPACE : 137' @@ -401,7 +409,7 @@ t; - 'box.error.ALTER_SPACE : 12' - 'box.error.ACTIVE_TRANSACTION : 79' - 'box.error.EXACT_FIELD_COUNT : 38' - - 'box.error.FUNCTION_MAX : 54' + - 'box.error.DROP_SEQUENCE : 144' - 'box.error.DROP_SPACE : 11' - 'box.error.UPSERT_UNIQUE_SECONDARY_KEY : 105' - 'box.error.UNKNOWN_REQUEST_TYPE : 48' @@ -423,7 +431,7 @@ t; - 'box.error.NO_CONNECTION : 77' - 'box.error.DROP_PRIMARY_KEY : 17' - 'box.error.TUPLE_FORMAT_LIMIT : 16' - - 'box.error.INJECTION : 8' + - 'box.error.NO_SUCH_SEQUENCE : 145' - 'box.error.PROC_RET : 21' - 'box.error.INVALID_UUID : 64' - 'box.error.INVALID_ORDER : 68' diff --git a/test/box/sequence.result b/test/box/sequence.result new file mode 100644 index 0000000000000000000000000000000000000000..a3e91282ade6187414510f835df2abd0ddf65ffe --- /dev/null +++ b/test/box/sequence.result @@ -0,0 +1,625 @@ +test_run = require('test_run').new() +--- +... +-- Options check on create. +box.schema.sequence.create('test', {abc = 'abc'}) +--- +- error: Illegal parameters, unexpected option 'abc' +... +box.schema.sequence.create('test', {step = 'a'}) +--- +- error: Illegal parameters, options parameter 'step' should be of type number +... +box.schema.sequence.create('test', {min = 'b'}) +--- +- error: Illegal parameters, options parameter 'min' should be of type number +... +box.schema.sequence.create('test', {max = 'c'}) +--- +- error: Illegal parameters, options parameter 'max' should be of type number +... +box.schema.sequence.create('test', {start = true}) +--- +- error: Illegal parameters, options parameter 'start' should be of type number +... +box.schema.sequence.create('test', {cycle = 123}) +--- +- error: Illegal parameters, options parameter 'cycle' should be of type boolean +... +box.schema.sequence.create('test', {name = 'test'}) +--- +- error: Illegal parameters, unexpected option 'name' +... +box.schema.sequence.create('test', {step = 0}) +--- +- error: 'Failed to create sequence ''test'': step option must be non-zero' +... +box.schema.sequence.create('test', {min = 10, max = 1}) +--- +- error: 'Failed to create sequence ''test'': max must be greater than or equal to + min' +... +box.schema.sequence.create('test', {min = 10, max = 20, start = 1}) +--- +- error: 'Failed to create sequence ''test'': start must be between min and max' +... +-- Options check on alter. +_ = box.schema.sequence.create('test') +--- +... +box.schema.sequence.alter('test', {abc = 'abc'}) +--- +- error: Illegal parameters, unexpected option 'abc' +... +box.schema.sequence.alter('test', {step = 'a'}) +--- +- error: Illegal parameters, options parameter 'step' should be of type number +... +box.schema.sequence.alter('test', {min = 'b'}) +--- +- error: Illegal parameters, options parameter 'min' should be of type number +... +box.schema.sequence.alter('test', {max = 'c'}) +--- +- error: Illegal parameters, options parameter 'max' should be of type number +... +box.schema.sequence.alter('test', {start = true}) +--- +- error: Illegal parameters, options parameter 'start' should be of type number +... +box.schema.sequence.alter('test', {cycle = 123}) +--- +- error: Illegal parameters, options parameter 'cycle' should be of type boolean +... +box.schema.sequence.alter('test', {name = 'test'}) +--- +... +box.schema.sequence.alter('test', {if_not_exists = false}) +--- +- error: Illegal parameters, unexpected option 'if_not_exists' +... +box.schema.sequence.alter('test', {step = 0}) +--- +- error: 'Can''t modify sequence ''test'': step option must be non-zero' +... +box.schema.sequence.alter('test', {min = 10, max = 1}) +--- +- error: 'Can''t modify sequence ''test'': max must be greater than or equal to min' +... +box.schema.sequence.alter('test', {min = 10, max = 20, start = 1}) +--- +- error: 'Can''t modify sequence ''test'': start must be between min and max' +... +box.schema.sequence.drop('test') +--- +... +-- Duplicate name. +sq1 = box.schema.sequence.create('test') +--- +... +box.schema.sequence.create('test') +--- +- error: Sequence 'test' already exists +... +sq2, msg = box.schema.sequence.create('test', {if_not_exists = true}) +--- +... +sq1 == sq2, msg +--- +- true +- not created +... +_ = box.schema.sequence.create('test2') +--- +... +box.schema.sequence.alter('test2', {name = 'test'}) +--- +- error: Duplicate key exists in unique index 'name' in space '_sequence' +... +box.schema.sequence.drop('test2') +--- +... +box.schema.sequence.drop('test') +--- +... +-- Check that box.sequence gets updated. +sq = box.schema.sequence.create('test') +--- +... +box.sequence.test == sq +--- +- true +... +sq.step +--- +- 1 +... +sq:alter{step = 2} +--- +... +box.sequence.test == sq +--- +- true +... +sq.step +--- +- 2 +... +sq:drop() +--- +... +box.sequence.test == nil +--- +- true +... +-- Attempt to delete a sequence that has a record in _sequence_data. +sq = box.schema.sequence.create('test') +--- +... +sq:next() +--- +- 1 +... +box.space._sequence:delete(sq.id) +--- +- error: 'Can''t drop sequence ''test'': the sequence has data' +... +box.space._sequence_data:delete(sq.id) +--- +- [1, 1] +... +box.space._sequence:delete(sq.id) +--- +- [1, 1, 'test', 1, 1, 9223372036854775807, 1, 0, false] +... +box.sequence.test == nil +--- +- true +... +-- Default ascending sequence. +sq = box.schema.sequence.create('test') +--- +... +sq.step, sq.min, sq.max, sq.start, sq.cycle +--- +- 1 +- 1 +- 9223372036854775807 +- 1 +- false +... +sq:get() -- error +--- +- error: Sequence 'test' is not started +... +sq:next() -- 1 +--- +- 1 +... +sq:get() -- 1 +--- +- 1 +... +sq:next() -- 2 +--- +- 2 +... +sq:set(100) +--- +... +sq:get() -- 100 +--- +- 100 +... +sq:next() -- 101 +--- +- 101 +... +sq:next() -- 102 +--- +- 102 +... +sq:reset() +--- +... +sq:get() -- error +--- +- error: Sequence 'test' is not started +... +sq:next() -- 1 +--- +- 1 +... +sq:next() -- 2 +--- +- 2 +... +sq:drop() +--- +... +-- Default descending sequence. +sq = box.schema.sequence.create('test', {step = -1}) +--- +... +sq.step, sq.min, sq.max, sq.start, sq.cycle +--- +- -1 +- -9223372036854775808 +- -1 +- -1 +- false +... +sq:get() -- error +--- +- error: Sequence 'test' is not started +... +sq:next() -- -1 +--- +- -1 +... +sq:get() -- -1 +--- +- -1 +... +sq:next() -- -2 +--- +- -2 +... +sq:set(-100) +--- +... +sq:get() -- -100 +--- +- -100 +... +sq:next() -- -101 +--- +- -101 +... +sq:next() -- -102 +--- +- -102 +... +sq:reset() +--- +... +sq:get() -- error +--- +- error: Sequence 'test' is not started +... +sq:next() -- -1 +--- +- -1 +... +sq:next() -- -2 +--- +- -2 +... +sq:drop() +--- +... +-- Custom min/max. +sq = box.schema.sequence.create('test', {min = 10}) +--- +... +sq.step, sq.min, sq.max, sq.start, sq.cycle +--- +- 1 +- 10 +- 9223372036854775807 +- 10 +- false +... +sq:next() -- 10 +--- +- 10 +... +sq:next() -- 11 +--- +- 11 +... +sq:drop() +--- +... +sq = box.schema.sequence.create('test', {step = -1, max = 20}) +--- +... +sq.step, sq.min, sq.max, sq.start, sq.cycle +--- +- -1 +- -9223372036854775808 +- 20 +- 20 +- false +... +sq:next() -- 20 +--- +- 20 +... +sq:next() -- 19 +--- +- 19 +... +sq:drop() +--- +... +-- Custom start value. +sq = box.schema.sequence.create('test', {start = 1000}) +--- +... +sq.step, sq.min, sq.max, sq.start, sq.cycle +--- +- 1 +- 1 +- 9223372036854775807 +- 1000 +- false +... +sq:next() -- 1000 +--- +- 1000 +... +sq:next() -- 1001 +--- +- 1001 +... +sq:reset() +--- +... +sq:next() -- 1000 +--- +- 1000 +... +sq:next() -- 1001 +--- +- 1001 +... +sq:drop() +--- +... +-- Overflow and cycle. +sq = box.schema.sequence.create('test', {max = 2}) +--- +... +sq:next() -- 1 +--- +- 1 +... +sq:next() -- 2 +--- +- 2 +... +sq:next() -- error +--- +- error: Sequence 'test' has overflowed +... +sq:alter{cycle = true} +--- +... +sq:next() -- 1 +--- +- 1 +... +sq:next() -- 2 +--- +- 2 +... +sq:next() -- 1 +--- +- 1 +... +sq:alter{step = 2} +--- +... +sq:next() -- 1 +--- +- 1 +... +sq:alter{cycle = false} +--- +... +sq:next() -- error +--- +- error: Sequence 'test' has overflowed +... +sq:drop() +--- +... +-- Setting sequence value outside boundaries. +sq = box.schema.sequence.create('test') +--- +... +sq:alter{step = 1, min = 1, max = 10} +--- +... +sq:set(-100) +--- +... +sq:next() -- 1 +--- +- 1 +... +sq:set(100) +--- +... +sq:next() -- error +--- +- error: Sequence 'test' has overflowed +... +sq:reset() +--- +... +sq:next() -- 1 +--- +- 1 +... +sq:alter{min = 5, start = 5} +--- +... +sq:next() -- 5 +--- +- 5 +... +sq:reset() +--- +... +sq:alter{step = -1, min = 1, max = 10, start = 10} +--- +... +sq:set(100) +--- +... +sq:next() -- 10 +--- +- 10 +... +sq:set(-100) +--- +... +sq:next() -- error +--- +- error: Sequence 'test' has overflowed +... +sq:reset() +--- +... +sq:next() -- 10 +--- +- 10 +... +sq:alter{max = 5, start = 5} +--- +... +sq:next() -- 5 +--- +- 5 +... +sq:drop() +--- +... +-- number64 arguments. +INT64_MIN = tonumber64('-9223372036854775808') +--- +... +INT64_MAX = tonumber64('9223372036854775807') +--- +... +sq = box.schema.sequence.create('test', {step = INT64_MAX, min = INT64_MIN, max = INT64_MAX, start = INT64_MIN}) +--- +... +sq:next() -- -9223372036854775808 +--- +- -9223372036854775808 +... +sq:next() -- -1 +--- +- -1 +... +sq:next() -- 9223372036854775806 +--- +- 9223372036854775806 +... +sq:next() -- error +--- +- error: Sequence 'test' has overflowed +... +sq:alter{step = INT64_MIN, start = INT64_MAX} +--- +... +sq:reset() +--- +... +sq:next() -- 9223372036854775807 +--- +- 9223372036854775807 +... +sq:next() -- -1 +--- +- -1 +... +sq:next() -- error +--- +- error: Sequence 'test' has overflowed +... +sq:drop() +--- +... +-- Using in a transaction. +s = box.schema.space.create('test') +--- +... +_ = s:create_index('pk') +--- +... +sq1 = box.schema.sequence.create('sq1', {step = 1}) +--- +... +sq2 = box.schema.sequence.create('sq2', {step = -1}) +--- +... +test_run:cmd("setopt delimiter ';'") +--- +- true +... +box.begin() +s:insert{sq1:next(), sq2:next()} +s:insert{sq1:next(), sq2:next()} +s:insert{sq1:next(), sq2:next()} +box.rollback(); +--- +... +box.begin() +s:insert{sq1:next(), sq2:next()} +s:insert{sq1:next(), sq2:next()} +s:insert{sq1:next(), sq2:next()} +box.commit(); +--- +... +test_run:cmd("setopt delimiter ''"); +--- +- true +... +s:select() -- [4, -4], [5, -5], [6, -6] +--- +- - [4, -4] + - [5, -5] + - [6, -6] +... +sq1:drop() +--- +... +sq2:drop() +--- +... +s:drop() +--- +... +-- +-- Check that sequences are persistent. +-- +sq = box.schema.sequence.create('test', {step = 2, min = 10, max = 20, start = 15, cycle = true}) +--- +... +sq:next() +--- +- 15 +... +test_run:cmd('restart server default') +sq = box.sequence.test +--- +... +sq.step, sq.min, sq.max, sq.start, sq.cycle +--- +- 2 +- 10 +- 20 +- 15 +- true +... +sq:next() +--- +- 17 +... +sq:drop() +--- +... diff --git a/test/box/sequence.test.lua b/test/box/sequence.test.lua new file mode 100644 index 0000000000000000000000000000000000000000..ca1412f4b0f812a41e38aa76b866b687769ed3a0 --- /dev/null +++ b/test/box/sequence.test.lua @@ -0,0 +1,207 @@ +test_run = require('test_run').new() + +-- Options check on create. +box.schema.sequence.create('test', {abc = 'abc'}) +box.schema.sequence.create('test', {step = 'a'}) +box.schema.sequence.create('test', {min = 'b'}) +box.schema.sequence.create('test', {max = 'c'}) +box.schema.sequence.create('test', {start = true}) +box.schema.sequence.create('test', {cycle = 123}) +box.schema.sequence.create('test', {name = 'test'}) +box.schema.sequence.create('test', {step = 0}) +box.schema.sequence.create('test', {min = 10, max = 1}) +box.schema.sequence.create('test', {min = 10, max = 20, start = 1}) + +-- Options check on alter. +_ = box.schema.sequence.create('test') +box.schema.sequence.alter('test', {abc = 'abc'}) +box.schema.sequence.alter('test', {step = 'a'}) +box.schema.sequence.alter('test', {min = 'b'}) +box.schema.sequence.alter('test', {max = 'c'}) +box.schema.sequence.alter('test', {start = true}) +box.schema.sequence.alter('test', {cycle = 123}) +box.schema.sequence.alter('test', {name = 'test'}) +box.schema.sequence.alter('test', {if_not_exists = false}) +box.schema.sequence.alter('test', {step = 0}) +box.schema.sequence.alter('test', {min = 10, max = 1}) +box.schema.sequence.alter('test', {min = 10, max = 20, start = 1}) +box.schema.sequence.drop('test') + +-- Duplicate name. +sq1 = box.schema.sequence.create('test') +box.schema.sequence.create('test') +sq2, msg = box.schema.sequence.create('test', {if_not_exists = true}) +sq1 == sq2, msg +_ = box.schema.sequence.create('test2') +box.schema.sequence.alter('test2', {name = 'test'}) +box.schema.sequence.drop('test2') +box.schema.sequence.drop('test') + +-- Check that box.sequence gets updated. +sq = box.schema.sequence.create('test') +box.sequence.test == sq +sq.step +sq:alter{step = 2} +box.sequence.test == sq +sq.step +sq:drop() +box.sequence.test == nil + +-- Attempt to delete a sequence that has a record in _sequence_data. +sq = box.schema.sequence.create('test') +sq:next() +box.space._sequence:delete(sq.id) +box.space._sequence_data:delete(sq.id) +box.space._sequence:delete(sq.id) +box.sequence.test == nil + +-- Default ascending sequence. +sq = box.schema.sequence.create('test') +sq.step, sq.min, sq.max, sq.start, sq.cycle +sq:get() -- error +sq:next() -- 1 +sq:get() -- 1 +sq:next() -- 2 +sq:set(100) +sq:get() -- 100 +sq:next() -- 101 +sq:next() -- 102 +sq:reset() +sq:get() -- error +sq:next() -- 1 +sq:next() -- 2 +sq:drop() + +-- Default descending sequence. +sq = box.schema.sequence.create('test', {step = -1}) +sq.step, sq.min, sq.max, sq.start, sq.cycle +sq:get() -- error +sq:next() -- -1 +sq:get() -- -1 +sq:next() -- -2 +sq:set(-100) +sq:get() -- -100 +sq:next() -- -101 +sq:next() -- -102 +sq:reset() +sq:get() -- error +sq:next() -- -1 +sq:next() -- -2 +sq:drop() + +-- Custom min/max. +sq = box.schema.sequence.create('test', {min = 10}) +sq.step, sq.min, sq.max, sq.start, sq.cycle +sq:next() -- 10 +sq:next() -- 11 +sq:drop() +sq = box.schema.sequence.create('test', {step = -1, max = 20}) +sq.step, sq.min, sq.max, sq.start, sq.cycle +sq:next() -- 20 +sq:next() -- 19 +sq:drop() + +-- Custom start value. +sq = box.schema.sequence.create('test', {start = 1000}) +sq.step, sq.min, sq.max, sq.start, sq.cycle +sq:next() -- 1000 +sq:next() -- 1001 +sq:reset() +sq:next() -- 1000 +sq:next() -- 1001 +sq:drop() + +-- Overflow and cycle. +sq = box.schema.sequence.create('test', {max = 2}) +sq:next() -- 1 +sq:next() -- 2 +sq:next() -- error +sq:alter{cycle = true} +sq:next() -- 1 +sq:next() -- 2 +sq:next() -- 1 +sq:alter{step = 2} +sq:next() -- 1 +sq:alter{cycle = false} +sq:next() -- error +sq:drop() + +-- Setting sequence value outside boundaries. +sq = box.schema.sequence.create('test') + +sq:alter{step = 1, min = 1, max = 10} +sq:set(-100) +sq:next() -- 1 +sq:set(100) +sq:next() -- error +sq:reset() +sq:next() -- 1 +sq:alter{min = 5, start = 5} +sq:next() -- 5 +sq:reset() + +sq:alter{step = -1, min = 1, max = 10, start = 10} +sq:set(100) +sq:next() -- 10 +sq:set(-100) +sq:next() -- error +sq:reset() +sq:next() -- 10 +sq:alter{max = 5, start = 5} +sq:next() -- 5 +sq:drop() + +-- number64 arguments. +INT64_MIN = tonumber64('-9223372036854775808') +INT64_MAX = tonumber64('9223372036854775807') +sq = box.schema.sequence.create('test', {step = INT64_MAX, min = INT64_MIN, max = INT64_MAX, start = INT64_MIN}) +sq:next() -- -9223372036854775808 +sq:next() -- -1 +sq:next() -- 9223372036854775806 +sq:next() -- error +sq:alter{step = INT64_MIN, start = INT64_MAX} +sq:reset() +sq:next() -- 9223372036854775807 +sq:next() -- -1 +sq:next() -- error +sq:drop() + +-- Using in a transaction. +s = box.schema.space.create('test') +_ = s:create_index('pk') +sq1 = box.schema.sequence.create('sq1', {step = 1}) +sq2 = box.schema.sequence.create('sq2', {step = -1}) + +test_run:cmd("setopt delimiter ';'") +box.begin() +s:insert{sq1:next(), sq2:next()} +s:insert{sq1:next(), sq2:next()} +s:insert{sq1:next(), sq2:next()} +box.rollback(); +box.begin() +s:insert{sq1:next(), sq2:next()} +s:insert{sq1:next(), sq2:next()} +s:insert{sq1:next(), sq2:next()} +box.commit(); +test_run:cmd("setopt delimiter ''"); + +s:select() -- [4, -4], [5, -5], [6, -6] + +sq1:drop() +sq2:drop() +s:drop() + +-- +-- Check that sequences are persistent. +-- + +sq = box.schema.sequence.create('test', {step = 2, min = 10, max = 20, start = 15, cycle = true}) +sq:next() + +test_run:cmd('restart server default') + +sq = box.sequence.test +sq.step, sq.min, sq.max, sq.start, sq.cycle + +sq:next() +sq:drop() diff --git a/test/engine/iterator.result b/test/engine/iterator.result index df770cf5a593ec6123361a06978447d81e28720d..96516f7b18dbc8fe9ee9a05551d4ad00413565e3 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:715: usage: next(param, state)' +- error: 'builtin/box/schema.lua:743: usage: next(param, state)' ... value --- diff --git a/test/engine/savepoint.result b/test/engine/savepoint.result index d28c16ac57fb4cc9c95ba2f68f30b7766325cb96..9eb4a6df9618666f1c892af96bb844ce7d628971 100644 --- a/test/engine/savepoint.result +++ b/test/engine/savepoint.result @@ -14,7 +14,7 @@ s1 = box.savepoint() ... box.rollback_to_savepoint(s1) --- -- error: 'builtin/box/schema.lua:260: Usage: box.rollback_to_savepoint(savepoint)' +- error: 'builtin/box/schema.lua:288: Usage: box.rollback_to_savepoint(savepoint)' ... box.begin() s1 = box.savepoint() --- @@ -323,27 +323,27 @@ test_run:cmd("setopt delimiter ''"); ok1, errmsg1 --- - false -- 'builtin/box/schema.lua:260: Usage: box.rollback_to_savepoint(savepoint)' +- 'builtin/box/schema.lua:288: Usage: box.rollback_to_savepoint(savepoint)' ... ok2, errmsg2 --- - false -- 'builtin/box/schema.lua:260: Usage: box.rollback_to_savepoint(savepoint)' +- 'builtin/box/schema.lua:288: Usage: box.rollback_to_savepoint(savepoint)' ... ok3, errmsg3 --- - false -- 'builtin/box/schema.lua:260: Usage: box.rollback_to_savepoint(savepoint)' +- 'builtin/box/schema.lua:288: Usage: box.rollback_to_savepoint(savepoint)' ... ok4, errmsg4 --- - false -- 'builtin/box/schema.lua:260: Usage: box.rollback_to_savepoint(savepoint)' +- 'builtin/box/schema.lua:288: Usage: box.rollback_to_savepoint(savepoint)' ... ok5, errmsg5 --- - false -- 'builtin/box/schema.lua:260: Usage: box.rollback_to_savepoint(savepoint)' +- 'builtin/box/schema.lua:288: Usage: box.rollback_to_savepoint(savepoint)' ... s:select{} --- diff --git a/test/wal_off/alter.result b/test/wal_off/alter.result index 66d7167a4b174f460aaeb95c675ca6a8c7899188..7c5ae26f9089b184876bafc34e80a8f08e9d1f70 100644 --- a/test/wal_off/alter.result +++ b/test/wal_off/alter.result @@ -28,7 +28,7 @@ end; ... #spaces; --- -- 65520 +- 65518 ... -- cleanup for k, v in pairs(spaces) do diff --git a/test/xlog/upgrade.result b/test/xlog/upgrade.result index f37c34fdd2fb181b6f2751688ea82ff4261c3290..8e366a1481d1349b031069cd9838c02b84fb4354 100644 --- a/test/xlog/upgrade.result +++ b/test/xlog/upgrade.result @@ -49,6 +49,13 @@ box.space._space:select() 'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'engine', 'type': 'string'}, {'name': 'field_count', 'type': 'unsigned'}, {'name': 'flags', 'type': 'map'}, {'name': 'format', 'type': 'array'}]] + - [284, 1, '_sequence', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'owner', + 'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'step', + 'type': 'integer'}, {'name': 'min', 'type': 'integer'}, {'name': 'max', 'type': 'integer'}, + {'name': 'start', 'type': 'integer'}, {'name': 'cache', 'type': 'integer'}, + {'name': 'cycle', 'type': 'boolean'}]] + - [285, 1, '_sequence_data', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, + {'name': 'value', 'type': 'integer'}]] - [288, 1, '_index', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'iid', 'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'type', 'type': 'string'}, {'name': 'opts', 'type': 'map'}, {'name': 'parts', 'type': 'array'}]] @@ -90,6 +97,10 @@ box.space._index:select() - [281, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]] - [281, 1, 'owner', 'tree', {'unique': false}, [[1, 'unsigned']]] - [281, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]] + - [284, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]] + - [284, 1, 'owner', 'tree', {'unique': false}, [[1, 'unsigned']]] + - [284, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]] + - [285, 0, 'primary', 'hash', {'unique': true}, [[0, 'unsigned']]] - [288, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned'], [1, 'unsigned']]] - [288, 2, 'name', 'tree', {'unique': true}, [[0, 'unsigned'], [2, 'string']]] - [289, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned'], [1, 'unsigned']]]