diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt index 6c7ac2b2f11a7f2803d35e8e4289f46ef26f3a9c..08d5e374118da91019a09a12dcc9633f4e7dccbd 100644 --- a/src/box/CMakeLists.txt +++ b/src/box/CMakeLists.txt @@ -37,10 +37,13 @@ add_library(tuple STATIC tuple_compare.cc tuple_hash.cc key_def.cc + coll_def.c + coll.c + coll_cache.c field_def.c opt_def.c ) -target_link_libraries(tuple box_error core ${MSGPUCK_LIBRARIES} misc bit) +target_link_libraries(tuple box_error core ${MSGPUCK_LIBRARIES} ${ICU_LIBRARIES} misc bit) add_library(xlog STATIC xlog.c) target_link_libraries(xlog core box_error crc32 ${ZSTD_LIBRARIES}) @@ -86,8 +89,6 @@ add_library(box STATIC sequence.c func.c func_def.c - coll_def.c - coll.c alter.cc schema.cc schema_def.c diff --git a/src/box/alter.cc b/src/box/alter.cc index 4f30624f2d564a42b8545075b51144e07fa5922a..f62fe1a99ad0ba4f1e45d1bf7b42ac5f673a563d 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -34,6 +34,7 @@ #include "space.h" #include "memtx_index.h" #include "func.h" +#include "coll_cache.h" #include "txn.h" #include "tuple.h" #include "fiber.h" /* for gc_pool */ @@ -2043,6 +2044,183 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event) } } +/** Create a collation definition from tuple. */ +void +coll_def_new_from_tuple(const struct tuple *tuple, struct coll_def *def) +{ + memset(def, 0, sizeof(*def)); + uint32_t name_len, locale_len, type_len; + def->id = tuple_field_u32_xc(tuple, BOX_COLLATION_FIELD_ID); + def->name = tuple_field_str_xc(tuple, BOX_COLLATION_FIELD_NAME, &name_len); + def->name_len = name_len; + uint32_t owner_id = tuple_field_u32_xc(tuple, BOX_COLLATION_FIELD_UID); + const char *type = tuple_field_str_xc(tuple, BOX_COLLATION_FIELD_TYPE, + &type_len); + def->type = STRN2ENUM(coll_type, type, type_len); + if (def->type == coll_type_MAX) + tnt_raise(ClientError, ER_CANT_CREATE_COLLATION, + "unknown collation type"); + def->locale = tuple_field_str_xc(tuple, BOX_COLLATION_FIELD_LOCALE, + &locale_len); + def->locale_len = locale_len; + const char *options = + tuple_field_with_type_xc(tuple, BOX_COLLATION_FIELD_OPTIONS, + MP_MAP); + + if (name_len > BOX_NAME_MAX) + tnt_raise(ClientError, ER_CANT_CREATE_COLLATION, + "collation name is too long"); + if (locale_len > BOX_NAME_MAX) + tnt_raise(ClientError, ER_CANT_CREATE_COLLATION, + "collation locale is too long"); + + assert(def->type == COLL_TYPE_ICU); /* no more defined now */ + if (opts_decode(&def->icu, coll_icu_opts_reg, &options, + ER_WRONG_COLLATION_OPTIONS, + BOX_COLLATION_FIELD_OPTIONS, NULL) != 0) + diag_raise(); + + if (def->icu.french_collation == coll_icu_on_off_MAX) { + tnt_raise(ClientError, ER_CANT_CREATE_COLLATION, + "ICU wrong french_collation option setting, " + "expected ON | OFF"); + } + + if (def->icu.alternate_handling == coll_icu_alternate_handling_MAX) { + tnt_raise(ClientError, ER_CANT_CREATE_COLLATION, + "ICU wrong alternate_handling option setting, " + "expected NON_IGNORABLE | SHIFTED"); + } + + if (def->icu.case_first == coll_icu_case_first_MAX) { + tnt_raise(ClientError, ER_CANT_CREATE_COLLATION, + "ICU wrong case_first option setting, " + "expected OFF | UPPER_FIRST | LOWER_FIRST"); + } + + if (def->icu.case_level == coll_icu_on_off_MAX) { + tnt_raise(ClientError, ER_CANT_CREATE_COLLATION, + "ICU wrong case_level option setting, " + "expected ON | OFF"); + } + + if (def->icu.normalization_mode == coll_icu_on_off_MAX) { + tnt_raise(ClientError, ER_CANT_CREATE_COLLATION, + "ICU wrong normalization_mode option setting, " + "expected ON | OFF"); + } + + if (def->icu.strength == coll_icu_strength_MAX) { + tnt_raise(ClientError, ER_CANT_CREATE_COLLATION, + "ICU wrong strength option setting, " + "expected PRIMARY | SECONDARY | " + "TERTIARY | QUATERNARY | IDENTICAL"); + } + + if (def->icu.numeric_collation == coll_icu_on_off_MAX) { + tnt_raise(ClientError, ER_CANT_CREATE_COLLATION, + "ICU wrong numeric_collation option setting, " + "expected ON | OFF"); + } + + access_check_ddl(owner_id, SC_COLLATION); + +} + +/** Rollback change in collation space. */ +static void +coll_cache_rollback(struct trigger *trigger, void *event) +{ + struct coll *old_coll = (struct coll *)trigger->data; + struct txn_stmt *stmt = txn_last_stmt((struct txn*) event); + struct tuple *new_tuple = stmt->new_tuple; + + if (new_tuple != NULL) { + uint32_t new_id = tuple_field_u32_xc(new_tuple, + BOX_COLLATION_FIELD_ID); + struct coll *new_coll = coll_cache_find(new_id); + coll_cache_delete(new_coll); + coll_delete(new_coll); + } + + if (old_coll != NULL) { + struct coll *replaced; + int rc = coll_cache_replace(old_coll, &replaced); + assert(rc == 0 && replaced == NULL); + (void)rc; + } +} + +/** Delete a collation. */ +static void +coll_cache_delete_coll(struct trigger *trigger, void */* event */) +{ + struct coll *old_coll = (struct coll *)trigger->data; + coll_delete(old_coll); +} + +/** + * A trigger invoked on replace in a space containing + * collations that a user defined. + */ +static void +on_replace_dd_collation(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; + + struct coll *old_coll = NULL; + if (old_tuple != NULL) { + /* TODO: Check that no index uses the collation */ + uint32_t old_id = tuple_field_u32_xc(old_tuple, + BOX_COLLATION_FIELD_ID); + old_coll = coll_cache_find(old_id); + assert(old_coll != NULL); + access_check_ddl(old_coll->owner_id, SC_COLLATION); + + struct trigger *on_commit = + txn_alter_trigger_new(coll_cache_delete_coll, old_coll); + txn_on_commit(txn, on_commit); + } + + if (new_tuple == NULL) { + /* Simple DELETE */ + assert(old_tuple != NULL); + coll_cache_delete(old_coll); + + struct trigger *on_rollback = + txn_alter_trigger_new(coll_cache_rollback, old_coll); + txn_on_rollback(txn, on_rollback); + return; + } + + struct coll_def new_def; + coll_def_new_from_tuple(new_tuple, &new_def); + struct coll *new_coll = coll_new(&new_def); + if (new_coll == NULL) + diag_raise(); + auto def_guard = make_scoped_guard([=] { coll_delete(new_coll); }); + + struct coll *replaced; + if (coll_cache_replace(new_coll, &replaced) != 0) + diag_raise(); + if (replaced == NULL && old_coll != NULL) { + /* + * ID of a collation was changed. + * Remove collation by old ID. + */ + coll_cache_delete(old_coll); + } + + struct trigger *on_rollback = + txn_alter_trigger_new(coll_cache_rollback, old_coll); + txn_on_rollback(txn, on_rollback); + + def_guard.is_active = false; +} + /** * Create a privilege definition from tuple. */ @@ -2687,6 +2865,10 @@ struct trigger on_replace_func = { RLIST_LINK_INITIALIZER, on_replace_dd_func, NULL, NULL }; +struct trigger on_replace_collation = { + RLIST_LINK_INITIALIZER, on_replace_dd_collation, NULL, NULL +}; + struct trigger on_replace_priv = { RLIST_LINK_INITIALIZER, on_replace_dd_priv, NULL, NULL }; diff --git a/src/box/alter.h b/src/box/alter.h index 423648793603e006bf98ce3a97f508f184893245..fb5f65a68a31e7c575c30d0a132b415fa996b321 100644 --- a/src/box/alter.h +++ b/src/box/alter.h @@ -38,6 +38,7 @@ extern struct trigger on_replace_truncate; extern struct trigger on_replace_schema; extern struct trigger on_replace_user; extern struct trigger on_replace_func; +extern struct trigger on_replace_collation; extern struct trigger on_replace_priv; extern struct trigger on_replace_cluster; extern struct trigger on_replace_sequence; diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap index a8098ed560e3aace1126cf15c73c36f80d4287ad..4877919ffcfb8e5e08fd9d6bd22b12a63b9dd4a9 100644 Binary files a/src/box/bootstrap.snap and b/src/box/bootstrap.snap differ diff --git a/src/box/coll_cache.c b/src/box/coll_cache.c new file mode 100644 index 0000000000000000000000000000000000000000..137f5d717c25ce74de4034ae3bf8fda60423f619 --- /dev/null +++ b/src/box/coll_cache.c @@ -0,0 +1,101 @@ +/* + * Copyright 2010-2016, 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 "coll_cache.h" +#include "diag.h" +#include "assoc.h" + +/** mhash table (id -> collation) */ +static struct mh_i32ptr_t *coll_cache_id = NULL; + +/** Create global hash tables if necessary. */ +int +coll_cache_init() +{ + coll_cache_id = mh_i32ptr_new(); + if (coll_cache_id == NULL) { + diag_set(OutOfMemory, sizeof(*coll_cache_id), "malloc", + "coll_cache"); + return -1; + } + return 0; +} + +/** Delete global hash tables. */ +void +coll_cache_destroy() +{ + mh_i32ptr_delete(coll_cache_id); +} + +/** + * Insert or replace a collation into collation cache. + * @param coll - collation to insert/replace. + * @return - NULL if inserted, replaced collation if replaced. + */ +int +coll_cache_replace(struct coll *coll, struct coll **replaced) +{ + const struct mh_i32ptr_node_t node = {coll->id, coll}; + struct mh_i32ptr_node_t repl_node = {0, NULL}; + struct mh_i32ptr_node_t *prepl_node = &repl_node; + if (mh_i32ptr_put(coll_cache_id, &node, &prepl_node, NULL) == + mh_end(coll_cache_id)) { + diag_set(OutOfMemory, sizeof(node), "malloc", "coll_cache"); + return -1; + } + *replaced = repl_node.val; + return 0; +} + +/** + * Delete a collation from collation cache. + * @param coll - collation to delete. + */ +void +coll_cache_delete(const struct coll *coll) +{ + mh_int_t i = mh_i32ptr_find(coll_cache_id, coll->id, NULL); + if (i == mh_end(coll_cache_id)) + return; + mh_i32ptr_del(coll_cache_id, i, NULL); +} + +/** + * Find a collation object by its id. + */ +struct coll * +coll_cache_find(uint32_t id) +{ + mh_int_t pos = mh_i32ptr_find(coll_cache_id, id, NULL); + if (pos == mh_end(coll_cache_id)) + return NULL; + return mh_i32ptr_node(coll_cache_id, pos)->val; +} diff --git a/src/box/coll_cache.h b/src/box/coll_cache.h new file mode 100644 index 0000000000000000000000000000000000000000..b982ec285059e450e6e1b972bc8a8edaf6a0e6c3 --- /dev/null +++ b/src/box/coll_cache.h @@ -0,0 +1,77 @@ +#ifndef TARANTOOL_BOX_COLL_CACHE_H_INCLUDED +#define TARANTOOL_BOX_COLL_CACHE_H_INCLUDED +/* + * Copyright 2010-2016, 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 "coll.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* defined(__cplusplus) */ + +/** + * Create global hash tables. + * @return - 0 on success, -1 on memory error. + */ +int +coll_cache_init(); + +/** Delete global hash tables. */ +void +coll_cache_destroy(); + +/** + * Insert or replace a collation into collation cache. + * @param coll - collation to insert/replace. + * @param replaced - collation that was replaced. + * @return - 0 on success, -1 on memory error. + */ +int +coll_cache_replace(struct coll *coll, struct coll **replaced); + +/** + * Delete a collation from collation cache. + * @param coll - collation to delete. + */ +void +coll_cache_delete(const struct coll *coll); + +/** + * Find a collation object by its id. + */ +struct coll * +coll_cache_find(uint32_t id); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif /* defined(__cplusplus) */ + +#endif /* TARANTOOL_BOX_COLL_CACHE_H_INCLUDED */ diff --git a/src/box/coll_def.c b/src/box/coll_def.c index 1a469b70d5b37787b059c9ca3b13cb6803f1844b..f849845b3afa630afaf954386989228959c2d3fa 100644 --- a/src/box/coll_def.c +++ b/src/box/coll_def.c @@ -63,3 +63,48 @@ const char *coll_icu_strength_strs[] = { "IDENTICAL" }; +static int64_t +icu_on_off_from_str(const char *str, uint32_t len) +{ + return strnindex(coll_icu_on_off_strs + 1, str, len, + coll_icu_on_off_MAX - 1) + 1; +} + +static int64_t +icu_alternate_handling_from_str(const char *str, uint32_t len) +{ + return strnindex(coll_icu_alternate_handling_strs + 1, str, len, + coll_icu_alternate_handling_MAX - 1) + 1; +} + +static int64_t +icu_case_first_from_str(const char *str, uint32_t len) +{ + return strnindex(coll_icu_case_first_strs + 1, str, len, + coll_icu_case_first_MAX - 1) + 1; +} + +static int64_t +icu_strength_from_str(const char *str, uint32_t len) +{ + return strnindex(coll_icu_strength_strs + 1, str, len, + coll_icu_strength_MAX - 1) + 1; +} + +const struct opt_def coll_icu_opts_reg[] = { + OPT_DEF_ENUM("french_collation", coll_icu_on_off, struct coll_icu_def, + french_collation, icu_on_off_from_str), + OPT_DEF_ENUM("alternate_handling", coll_icu_alternate_handling, struct coll_icu_def, + alternate_handling, icu_alternate_handling_from_str), + OPT_DEF_ENUM("case_first", coll_icu_case_first, struct coll_icu_def, + case_first, icu_case_first_from_str), + OPT_DEF_ENUM("case_level", coll_icu_on_off, struct coll_icu_def, + case_level, icu_on_off_from_str), + OPT_DEF_ENUM("normalization_mode", coll_icu_on_off, struct coll_icu_def, + normalization_mode, icu_on_off_from_str), + OPT_DEF_ENUM("strength", coll_icu_strength, struct coll_icu_def, + strength, icu_strength_from_str), + OPT_DEF_ENUM("numeric_collation", coll_icu_on_off, struct coll_icu_def, + numeric_collation, icu_on_off_from_str), + OPT_END, +}; diff --git a/src/box/coll_def.h b/src/box/coll_def.h index f62e794033241cd80f3dbcd9fb507e0b13c7b099..7a1027a1e10d17c711602a135c459ee98adf0945 100644 --- a/src/box/coll_def.h +++ b/src/box/coll_def.h @@ -33,6 +33,7 @@ #include <stddef.h> #include <stdint.h> +#include "opt_def.h" #if defined(__cplusplus) extern "C" { @@ -128,6 +129,8 @@ struct coll_def { struct coll_icu_def icu; }; +extern const struct opt_def coll_icu_opts_reg[]; + #if defined(__cplusplus) } /* extern "C" */ #endif /* defined(__cplusplus) */ diff --git a/src/box/errcode.h b/src/box/errcode.h index aaaa9127c546fd3b4b77fabba802a1ea13bb227b..bca4cbd0c705c4d24ea4c8afb06bbb92d496f577 100644 --- a/src/box/errcode.h +++ b/src/box/errcode.h @@ -203,6 +203,7 @@ struct errcode_record { /*148 */_(ER_SEQUENCE_ACCESS_DENIED, "%s access is denied for user '%s' to sequence '%s'") \ /*149 */_(ER_SPACE_FIELD_IS_DUPLICATE, "Space field '%s' is duplicate") \ /*150 */_(ER_CANT_CREATE_COLLATION, "Failed to initialize collation: %s.") \ + /*151 */_(ER_WRONG_COLLATION_OPTIONS, "Wrong collation options (field %u): %s") \ /* * !IMPORTANT! Please follow instructions at start of the file diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua index 1ee6b0dc5c13776e3c30ae31764905a8f7e0b2fb..73b82a2e4d8d86682e5b09135a0ea0e1921e62b3 100644 --- a/src/box/lua/schema.lua +++ b/src/box/lua/schema.lua @@ -1611,6 +1611,66 @@ end box.schema.func.reload = internal.func_reload +box.internal.collation = {} +box.internal.collation.create = function(name, coll_type, locale, opts) + opts = opts or setmap{} + if type(name) ~= 'string' then + box.error(box.error.ILLEGAL_PARAMS, + "name (first arg) must be a string") + end + if type(coll_type) ~= 'string' then + box.error(box.error.ILLEGAL_PARAMS, + "type (second arg) must be a string") + end + if type(locale) ~= 'string' then + box.error(box.error.ILLEGAL_PARAMS, + "locale (third arg) must be a string") + end + if type(opts) ~= 'table' then + box.error(box.error.ILLEGAL_PARAMS, + "options (fourth arg) must be a table or nil") + end + local lua_opts = {if_not_exists = opts.if_not_exists } + check_param_table(lua_opts, {if_not_exists = 'boolean'}) + opts.if_not_exists = nil + opts = setmap(opts) + + local _coll = box.space[box.schema.COLLATION_ID] + if lua_opts.if_not_exists then + local coll = _coll.index.name:get{name} + if coll then + return + end + end + _coll:auto_increment{name, session.uid(), coll_type, locale, opts} +end + +box.internal.collation.drop = function(name, opts) + opts = opts or {} + check_param_table(opts, { if_exists = 'boolean' }) + + local _coll = box.space[box.schema.COLLATION_ID] + if opts.if_exists then + local coll = _coll.index.name:get{name} + if not coll then + return + end + end + _coll.index.name:delete{name} +end + +box.internal.collation.exists = function(name) + local _coll = box.space[box.schema.COLLATION_ID] + local coll = _coll.index.name:get{name} + return not not coll +end + +box.internal.collation.id_by_name = function(name) + local _coll = box.space[box.schema.COLLATION_ID] + local coll = _coll.index.name:get{name} + return coll[1] +end + box.schema.user = {} box.schema.user.password = function(password) diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc index 94a88c7e58e6a87d6fe1d91949a12593d9c155dd..3da7934b9413b92ed47e0d2eaf1ddaa9bda20386 100644 --- a/src/box/lua/space.cc +++ b/src/box/lua/space.cc @@ -351,6 +351,8 @@ box_lua_space_init(struct lua_State *L) lua_setfield(L, -2, "VUSER_ID"); lua_pushnumber(L, BOX_FUNC_ID); lua_setfield(L, -2, "FUNC_ID"); + lua_pushnumber(L, BOX_COLLATION_ID); + lua_setfield(L, -2, "COLLATION_ID"); lua_pushnumber(L, BOX_VFUNC_ID); lua_setfield(L, -2, "VFUNC_ID"); lua_pushnumber(L, BOX_PRIV_ID); diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua index 537e282c84291acbfe20a12356f49a2182425053..ae382eda9c455a27a3021de1d023a75cbebe7a69 100644 --- a/src/box/lua/upgrade.lua +++ b/src/box/lua/upgrade.lua @@ -837,8 +837,6 @@ 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] @@ -882,8 +880,32 @@ local function create_sequence_space() _index:insert{_space_sequence.id, 1, 'sequence', 'tree', {unique = false}, {{1, 'unsigned'}}} end +local function create_collation_space() + local _collation = box.space[box.schema.COLLATION_ID] + + log.info("create space _collation") + box.space._space:insert{_collation.id, ADMIN, '_collation', 'memtx', 0, setmap({}), + { { name = 'id', type = 'unsigned' }, { name = 'name', type = 'string' }, + { name = 'owner', type = 'unsigned' }, { name = 'type', type = 'string' }, + { name = 'locale', type = 'string' }, { name = 'opts', type = 'map' } } } + + log.info("create index primary on _collation") + box.space._index:insert{_collation.id, 0, 'primary', 'tree', {unique = true}, {{0, 'unsigned'}}} + + log.info("create index name on _collation") + box.space._index:insert{_collation.id, 1, 'name', 'tree', {unique = true}, {{1, 'string'}}} + + log.info("create predefined collations") + box.space._collation:replace{0, "unicode", ADMIN, "ICU", "", setmap{}} + box.space._collation:replace{1, "unicode_s1", ADMIN, "ICU", "", {strength='primary'}} + + local _priv = box.space[box.schema.PRIV_ID] + _priv:insert{ADMIN, PUBLIC, 'space', _collation.id, 2} +end + local function upgrade_to_1_7_6() create_sequence_space() + create_collation_space() -- Trigger space format checking by updating version in _schema. end @@ -912,7 +934,7 @@ local function upgrade(options) {version = mkversion(1, 7, 1), func = upgrade_to_1_7_1, auto = false}, {version = mkversion(1, 7, 2), func = upgrade_to_1_7_2, auto = false}, {version = mkversion(1, 7, 5), func = upgrade_to_1_7_5, auto = true}, - {version = mkversion(1, 7, 6), func = upgrade_to_1_7_6, auto = false} + {version = mkversion(1, 7, 6), func = upgrade_to_1_7_6, auto = false}, } for _, handler in ipairs(handlers) do diff --git a/src/box/schema.cc b/src/box/schema.cc index 71f3621678ac3c9f017455d40e3192314fa370cc..ff8ce0dc9cb460dec37c67ba714f6a94a61f0f38 100644 --- a/src/box/schema.cc +++ b/src/box/schema.cc @@ -279,6 +279,11 @@ schema_init() /* _space - home for all spaces. */ key_def_set_part(key_def, 0 /* part no */, 0 /* field no */, FIELD_TYPE_UNSIGNED); + + /* _collation - collation description. */ + sc_space_new(BOX_COLLATION_ID, "_collation", key_def, + &on_replace_collation, NULL); + sc_space_new(BOX_SPACE_ID, "_space", key_def, &alter_space_on_replace_space, &on_stmt_begin_space); diff --git a/src/box/schema_def.c b/src/box/schema_def.c index ad3d6832fbb0a0c2076e63c95b2a4fe1c121737e..492c593dba98ac20c22a85e91a6d28e1f7692f1d 100644 --- a/src/box/schema_def.c +++ b/src/box/schema_def.c @@ -40,6 +40,7 @@ static const char *object_type_strs[] = { /* [SC_USER] = */ "user", /* [SC_ROLE] = */ "role", /* [SC_SEQUENCE] = */ "sequence", + /* [SC_COLLATION] = */ "collation", }; enum schema_object_type diff --git a/src/box/schema_def.h b/src/box/schema_def.h index b97c81c7de1789e3f88340118c8399022446cfcf..528b8c4c2ce91cd7529749afc0783d1b97e1069f 100644 --- a/src/box/schema_def.h +++ b/src/box/schema_def.h @@ -68,6 +68,8 @@ enum { BOX_SYSTEM_ID_MIN = 256, /** Space id of _schema. */ BOX_SCHEMA_ID = 272, + /** Space id of _collation. */ + BOX_COLLATION_ID = 276, /** Space id of _space. */ BOX_SPACE_ID = 280, /** Space id of _vspace view. */ @@ -155,6 +157,16 @@ enum { BOX_FUNC_FIELD_LANGUAGE = 4, }; +/** _collation fields. */ +enum { + BOX_COLLATION_FIELD_ID = 0, + BOX_COLLATION_FIELD_NAME = 1, + BOX_COLLATION_FIELD_UID = 2, + BOX_COLLATION_FIELD_TYPE = 3, + BOX_COLLATION_FIELD_LOCALE = 4, + BOX_COLLATION_FIELD_OPTIONS = 5, +}; + /** _schema fields. */ enum { BOX_SCHEMA_FIELD_KEY = 0, @@ -213,6 +225,8 @@ enum schema_object_type { SC_USER = 4, SC_ROLE = 5, SC_SEQUENCE = 6, + SC_COLLATION = 7, + schema_object_type_MAX = 8 }; enum schema_object_type diff --git a/src/box/tuple.c b/src/box/tuple.c index 98ce12dd92f8ff6168e2632d97b1ed888039a23e..0455469043dbc5272675e7095b0306c60218d49a 100644 --- a/src/box/tuple.c +++ b/src/box/tuple.c @@ -38,6 +38,7 @@ #include "small/small.h" #include "tuple_update.h" +#include "coll_cache.h" static struct mempool tuple_iterator_pool; static struct small_alloc runtime_alloc; @@ -402,6 +403,9 @@ tuple_init(field_name_hash_f hash) box_tuple_last = NULL; + if (coll_cache_init() != 0) + return -1; + return 0; } @@ -451,6 +455,8 @@ tuple_free(void) small_alloc_destroy(&runtime_alloc); tuple_format_free(); + + coll_cache_destroy(); } box_tuple_format_t * diff --git a/src/trivia/util.h b/src/trivia/util.h index beaa4ad1dc7cb4011ff213b9b6acfb17cbc3fb8b..08b89f9beb715cf340eacbc9636ed8c3164c56a9 100644 --- a/src/trivia/util.h +++ b/src/trivia/util.h @@ -92,6 +92,7 @@ extern "C" { const char *enum_name##_strs[(unsigned) enum_name##_MAX + 1] = {enum_members(ENUM_STRS_MEMBER) 0} #endif #define STR2ENUM(enum_name, str) ((enum enum_name) strindex(enum_name##_strs, str, enum_name##_MAX)) +#define STRN2ENUM(enum_name, str, len) ((enum enum_name) strnindex(enum_name##_strs, str, len, enum_name##_MAX)) uint32_t strindex(const char **haystack, const char *needle, uint32_t hmax); diff --git a/test/app-tap/tarantoolctl.test.lua b/test/app-tap/tarantoolctl.test.lua index 38945ec5ba89cd678a243c164e4073ed1162786d..d75753012924d02e697c423a4065ca3008e42f95 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", 16) - check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 38) + check_ctlcat_snap(test_i, dir, "--space=280", "---\n", 17) + check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 40) end) end) diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result index 0994b084dbf5e31d4d3dea76479fb0c39eb159f1..d5fa6579fb60ca0e49262e5c5767ca30b1ff6619 100644 --- a/test/box-py/bootstrap.result +++ b/test/box-py/bootstrap.result @@ -14,6 +14,10 @@ box.space._cluster:select{} box.space._space:select{} --- - - [272, 1, '_schema', 'memtx', 0, {}, [{'type': 'string', 'name': 'key'}]] + - [276, 1, '_collation', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, { + 'name': 'name', 'type': 'string'}, {'name': 'owner', 'type': 'unsigned'}, + {'name': 'type', 'type': 'string'}, {'name': 'locale', 'type': 'string'}, { + 'name': 'opts', 'type': 'map'}]] - [280, 1, '_space', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'owner', 'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'engine', 'type': 'string'}, {'name': 'field_count', 'type': 'unsigned'}, {'name': 'flags', @@ -63,6 +67,8 @@ box.space._space:select{} box.space._index:select{} --- - - [272, 0, 'primary', 'tree', {'unique': true}, [[0, 'string']]] + - [276, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]] + - [276, 1, 'name', 'tree', {'unique': true}, [[1, 'string']]] - [280, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]] - [280, 1, 'owner', 'tree', {'unique': false}, [[1, 'unsigned']]] - [280, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]] @@ -119,6 +125,7 @@ box.space._priv:select{} - - [1, 0, 'role', 2, 4] - [1, 1, 'universe', 0, 7] - [1, 2, 'function', 1, 4] + - [1, 2, 'space', 276, 2] - [1, 2, 'space', 281, 1] - [1, 2, 'space', 289, 1] - [1, 2, 'space', 297, 1] diff --git a/test/box/access.result b/test/box/access.result index 8337bf1cd522db7650152141557234216c2778b2..d1bef1d7f367ed8a97e702566b9d9739319f0b30 100644 --- a/test/box/access.result +++ b/test/box/access.result @@ -780,3 +780,43 @@ c:_request("select", nil, 4294967295, box.index.EQ, 0, 0, 0xFFFFFFFF, {}) c:close() --- ... +session = box.session +--- +... +box.schema.user.create('test') +--- +... +box.schema.user.grant('test', 'read,write', 'universe') +--- +... +session.su('test') +--- +... +box.internal.collation.create('test', 'ICU', 'ru_RU') +--- +... +session.su('admin') +--- +... +box.internal.collation.drop('test') -- success +--- +... +box.internal.collation.create('test', 'ICU', 'ru_RU') +--- +... +session.su('test') +--- +... +box.internal.collation.drop('test') -- fail +--- +- error: Create, drop or alter access on collation is denied for user 'test' +... +session.su('admin') +--- +... +box.internal.collation.drop('test') -- success +--- +... +box.schema.user.drop('test') +--- +... diff --git a/test/box/access.test.lua b/test/box/access.test.lua index cde4e0f9aa00aa04b4b4e4c946270d5f4e4c3062..761bb89041ebbe8b71530f9db5903f9f51c68b5a 100644 --- a/test/box/access.test.lua +++ b/test/box/access.test.lua @@ -303,3 +303,17 @@ c:_request("select", nil, 1, box.index.EQ, 0, 0, 0xFFFFFFFF, {}) c:_request("select", nil, 65537, box.index.EQ, 0, 0, 0xFFFFFFFF, {}) c:_request("select", nil, 4294967295, box.index.EQ, 0, 0, 0xFFFFFFFF, {}) c:close() + +session = box.session +box.schema.user.create('test') +box.schema.user.grant('test', 'read,write', 'universe') +session.su('test') +box.internal.collation.create('test', 'ICU', 'ru_RU') +session.su('admin') +box.internal.collation.drop('test') -- success +box.internal.collation.create('test', 'ICU', 'ru_RU') +session.su('test') +box.internal.collation.drop('test') -- fail +session.su('admin') +box.internal.collation.drop('test') -- success +box.schema.user.drop('test') diff --git a/test/box/access_misc.result b/test/box/access_misc.result index 2c585b06558ae0469f491b208f7907ffbeddf80e..d190c0e41c42eb6795d4315dc837acaabe89e4f9 100644 --- a/test/box/access_misc.result +++ b/test/box/access_misc.result @@ -623,6 +623,10 @@ box.space._user:select() box.space._space:select() --- - - [272, 1, '_schema', 'memtx', 0, {}, [{'type': 'string', 'name': 'key'}]] + - [276, 1, '_collation', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, { + 'name': 'name', 'type': 'string'}, {'name': 'owner', 'type': 'unsigned'}, + {'name': 'type', 'type': 'string'}, {'name': 'locale', 'type': 'string'}, { + 'name': 'opts', 'type': 'map'}]] - [280, 1, '_space', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'owner', 'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'engine', 'type': 'string'}, {'name': 'field_count', 'type': 'unsigned'}, {'name': 'flags', diff --git a/test/box/access_sysview.result b/test/box/access_sysview.result index 99ab3b954cada177450728806bc5002027166b6e..ca6e649b5717d3e3b441fbe5f5a172a9b6d5e422 100644 --- a/test/box/access_sysview.result +++ b/test/box/access_sysview.result @@ -138,11 +138,11 @@ box.session.su('guest') ... #box.space._vspace:select{} --- -- 6 +- 7 ... #box.space._vindex:select{} --- -- 15 +- 17 ... box.session.su('admin') --- @@ -230,11 +230,11 @@ box.session.su('guest') ... #box.space._vspace:select{} --- -- 17 +- 18 ... #box.space._vindex:select{} --- -- 39 +- 41 ... #box.space._vuser:select{} --- @@ -242,7 +242,7 @@ box.session.su('guest') ... #box.space._vpriv:select{} --- -- 12 +- 13 ... #box.space._vfunc:select{} --- @@ -262,7 +262,7 @@ box.session.su('guest') ... #box.space._vindex:select{} --- -- 39 +- 41 ... #box.space._vuser:select{} --- diff --git a/test/box/alter.result b/test/box/alter.result index d4050fa05995a86433848aa86f4978509a7f76ee..583937b1158b09e0ec005942de5f5e299b2902eb 100644 --- a/test/box/alter.result +++ b/test/box/alter.result @@ -182,6 +182,8 @@ box.space._vspace.index.owner:alter{parts = {2, 'unsigned'}} _index:select{} --- - - [272, 0, 'primary', 'tree', {'unique': true}, [[0, 'string']]] + - [276, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]] + - [276, 1, 'name', 'tree', {'unique': true}, [[1, 'string']]] - [280, 0, 'primary', 'tree', 1, 1, 0, 'unsigned'] - [280, 1, 'owner', 'tree', {'unique': false}, [{'field': 1, 'type': 'unsigned'}]] - [280, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]] diff --git a/test/box/ddl.result b/test/box/ddl.result index ed29097bc2c93d19a2acbbd822a04a156202d82d..1d9665bcf9fe750398a5ff74ae56f272c3182767 100644 --- a/test/box/ddl.result +++ b/test/box/ddl.result @@ -203,3 +203,276 @@ for i = 1, 2 do fiber.create(function() fiber.yield() space:format(format) ch:pu space:drop() --- ... +-- collation +function setmap(table) return setmetatable(table, { __serialize = 'map' }) end +--- +... +box.internal.collation.create('test') +--- +- error: Illegal parameters, type (second arg) must be a string +... +box.internal.collation.create('test', 'ICU') +--- +- error: Illegal parameters, locale (third arg) must be a string +... +box.internal.collation.create(42, 'ICU', 'ru_RU') +--- +- error: Illegal parameters, name (first arg) must be a string +... +box.internal.collation.create('test', 42, 'ru_RU') +--- +- error: Illegal parameters, type (second arg) must be a string +... +box.internal.collation.create('test', 'ICU', 42) +--- +- error: Illegal parameters, locale (third arg) must be a string +... +box.internal.collation.create('test', 'nothing', 'ru_RU') +--- +- error: 'Failed to initialize collation: unknown collation type.' +... +box.internal.collation.create('test', 'ICU', 'ru_RU', setmap{}) --ok +--- +... +box.internal.collation.create('test', 'ICU', 'ru_RU') +--- +- error: Duplicate key exists in unique index 'name' in space '_collation' +... +box.internal.collation.drop('test') +--- +... +box.internal.collation.drop('nothing') -- allowed +--- +... +box.internal.collation.create('test', 'ICU', 'ru_RU', 42) +--- +- error: Illegal parameters, options (fourth arg) must be a table or nil +... +box.internal.collation.create('test', 'ICU', 'ru_RU', 'options') +--- +- error: Illegal parameters, options (fourth arg) must be a table or nil +... +box.internal.collation.create('test', 'ICU', 'ru_RU', {ping='pong'}) +--- +- error: 'Wrong collation options (field 5): unexpected option ''ping''' +... +box.internal.collation.create('test', 'ICU', 'ru_RU', {french_collation='german'}) +--- +- error: 'Failed to initialize collation: ICU wrong french_collation option setting, + expected ON | OFF.' +... +box.internal.collation.create('test', 'ICU', 'ru_RU', {french_collation='on'}) --ok +--- +... +box.internal.collation.drop('test') --ok +--- +... +box.internal.collation.create('test', 'ICU', 'ru_RU', {strength='supervillian'}) +--- +- error: 'Failed to initialize collation: ICU wrong strength option setting, expected + PRIMARY | SECONDARY | TERTIARY | QUATERNARY | IDENTICAL.' +... +box.internal.collation.create('test', 'ICU', 'ru_RU', {strength=42}) +--- +- error: 'Wrong collation options (field 5): ''strength'' must be enum' +... +box.internal.collation.create('test', 'ICU', 'ru_RU', {strength=2}) --ok +--- +- error: 'Wrong collation options (field 5): ''strength'' must be enum' +... +box.internal.collation.drop('test') --ok +--- +... +box.internal.collation.create('test', 'ICU', 'ru_RU', {strength='primary'}) --ok +--- +... +box.internal.collation.drop('test') --ok +--- +... +box.internal.collation.create('test', 'ICU', 'ru_RU') +--- +... +box.internal.collation.exists('test') +--- +- true +... +test_run:cmd('restart server default') +function setmap(table) return setmetatable(table, { __serialize = 'map' }) end +--- +... +box.internal.collation.exists('test') +--- +- true +... +box.internal.collation.drop('test') +--- +... +box.space._collation:auto_increment{'test'} +--- +- error: Tuple field count 2 is less than required by a defined index (expected 6) +... +box.space._collation:auto_increment{'test', 0, 'ICU'} +--- +- error: Tuple field count 4 is less than required by a defined index (expected 6) +... +box.space._collation:auto_increment{'test', 'ADMIN', 'ICU', 'ru_RU'} +--- +- error: Tuple field count 5 is less than required by a defined index (expected 6) +... +box.space._collation:auto_increment{42, 0, 'ICU', 'ru_RU'} +--- +- error: Tuple field count 5 is less than required by a defined index (expected 6) +... +box.space._collation:auto_increment{'test', 0, 42, 'ru_RU'} +--- +- error: Tuple field count 5 is less than required by a defined index (expected 6) +... +box.space._collation:auto_increment{'test', 0, 'ICU', 42} +--- +- error: Tuple field count 5 is less than required by a defined index (expected 6) +... +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', setmap{}} --ok +--- +- [2, 'test', 0, 'ICU', 'ru_RU', {}] +... +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', setmap{}} +--- +- error: Duplicate key exists in unique index 'name' in space '_collation' +... +box.space._collation.index.name:delete{'test'} -- ok +--- +- [2, 'test', 0, 'ICU', 'ru_RU', {}] +... +box.space._collation.index.name:delete{'nothing'} -- allowed +--- +... +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', 42} +--- +- error: 'Tuple field 6 type does not match one required by operation: expected map' +... +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', 'options'} +--- +- error: 'Tuple field 6 type does not match one required by operation: expected map' +... +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', {ping='pong'}} +--- +- error: 'Wrong collation options (field 5): unexpected option ''ping''' +... +opts = {normalization_mode='NORMAL'} +--- +... +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} +--- +- error: 'Failed to initialize collation: ICU wrong normalization_mode option setting, + expected ON | OFF.' +... +opts.normalization_mode = 'OFF' +--- +... +_ = box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} -- ok +--- +... +_ = box.space._collation.index.name:delete{'test'} -- ok +--- +... +opts.numeric_collation = 'PERL' +--- +... +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} +--- +- error: 'Failed to initialize collation: ICU wrong numeric_collation option setting, + expected ON | OFF.' +... +opts.numeric_collation = 'ON' +--- +... +_ = box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} --ok +--- +... +_ = box.space._collation.index.name:delete{'test'} -- ok +--- +... +opts.alternate_handling1 = 'ON' +--- +... +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} +--- +- error: 'Wrong collation options (field 5): unexpected option ''alternate_handling1''' +... +opts.alternate_handling1 = nil +--- +... +opts.alternate_handling = 'ON' +--- +... +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} +--- +- error: 'Failed to initialize collation: ICU wrong alternate_handling option setting, + expected NON_IGNORABLE | SHIFTED.' +... +opts.alternate_handling = 'SHIFTED' +--- +... +_ = box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} --ok +--- +... +_ = box.space._collation.index.name:delete{'test'} -- ok +--- +... +opts.case_first = 'ON' +--- +... +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} +--- +- error: 'Failed to initialize collation: ICU wrong case_first option setting, expected + OFF | UPPER_FIRST | LOWER_FIRST.' +... +opts.case_first = 'OFF' +--- +... +_ = box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} --ok +--- +... +_ = box.space._collation.index.name:delete{'test'} -- ok +--- +... +opts.case_level = 'UPPER' +--- +... +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} +--- +- error: 'Failed to initialize collation: ICU wrong case_level option setting, expected + ON | OFF.' +... +opts.case_level = 'DEFAULT' +--- +... +_ = box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} --ok +--- +- error: 'Failed to initialize collation: ICU wrong case_level option setting, expected + ON | OFF.' +... +_ = box.space._collation.index.name:delete{'test'} -- ok +--- +... +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', setmap{}} +--- +- [2, 'test', 0, 'ICU', 'ru_RU', {}] +... +box.space._collation:select{} +--- +- - [0, 'unicode', 1, 'ICU', '', {}] + - [1, 'unicode_s1', 1, 'ICU', '', {'strength': 'primary'}] + - [2, 'test', 0, 'ICU', 'ru_RU', {}] +... +test_run:cmd('restart server default') +box.space._collation:select{} +--- +- - [0, 'unicode', 1, 'ICU', '', {}] + - [1, 'unicode_s1', 1, 'ICU', '', {'strength': 'primary'}] + - [2, 'test', 0, 'ICU', 'ru_RU', {}] +... +box.space._collation.index.name:delete{'test'} +--- +- [2, 'test', 0, 'ICU', 'ru_RU', {}] +... diff --git a/test/box/ddl.test.lua b/test/box/ddl.test.lua index 3d6ad008d9906dff0064caa971537ba1663d751e..34b71193c09382bc94b0da8fab12bbd2a4ff7bdb 100644 --- a/test/box/ddl.test.lua +++ b/test/box/ddl.test.lua @@ -101,3 +101,86 @@ for i = 1, 2 do fiber.create(function() fiber.yield() space:format(format) ch:pu {ch:get(), ch:get(), ch:get()} space:drop() + +-- collation +function setmap(table) return setmetatable(table, { __serialize = 'map' }) end + +box.internal.collation.create('test') +box.internal.collation.create('test', 'ICU') +box.internal.collation.create(42, 'ICU', 'ru_RU') +box.internal.collation.create('test', 42, 'ru_RU') +box.internal.collation.create('test', 'ICU', 42) +box.internal.collation.create('test', 'nothing', 'ru_RU') +box.internal.collation.create('test', 'ICU', 'ru_RU', setmap{}) --ok +box.internal.collation.create('test', 'ICU', 'ru_RU') +box.internal.collation.drop('test') +box.internal.collation.drop('nothing') -- allowed +box.internal.collation.create('test', 'ICU', 'ru_RU', 42) +box.internal.collation.create('test', 'ICU', 'ru_RU', 'options') +box.internal.collation.create('test', 'ICU', 'ru_RU', {ping='pong'}) +box.internal.collation.create('test', 'ICU', 'ru_RU', {french_collation='german'}) +box.internal.collation.create('test', 'ICU', 'ru_RU', {french_collation='on'}) --ok +box.internal.collation.drop('test') --ok +box.internal.collation.create('test', 'ICU', 'ru_RU', {strength='supervillian'}) +box.internal.collation.create('test', 'ICU', 'ru_RU', {strength=42}) +box.internal.collation.create('test', 'ICU', 'ru_RU', {strength=2}) --ok +box.internal.collation.drop('test') --ok +box.internal.collation.create('test', 'ICU', 'ru_RU', {strength='primary'}) --ok +box.internal.collation.drop('test') --ok + +box.internal.collation.create('test', 'ICU', 'ru_RU') +box.internal.collation.exists('test') + +test_run:cmd('restart server default') +function setmap(table) return setmetatable(table, { __serialize = 'map' }) end + +box.internal.collation.exists('test') +box.internal.collation.drop('test') + +box.space._collation:auto_increment{'test'} +box.space._collation:auto_increment{'test', 0, 'ICU'} +box.space._collation:auto_increment{'test', 'ADMIN', 'ICU', 'ru_RU'} +box.space._collation:auto_increment{42, 0, 'ICU', 'ru_RU'} +box.space._collation:auto_increment{'test', 0, 42, 'ru_RU'} +box.space._collation:auto_increment{'test', 0, 'ICU', 42} +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', setmap{}} --ok +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', setmap{}} +box.space._collation.index.name:delete{'test'} -- ok +box.space._collation.index.name:delete{'nothing'} -- allowed +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', 42} +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', 'options'} +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', {ping='pong'}} +opts = {normalization_mode='NORMAL'} +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} +opts.normalization_mode = 'OFF' +_ = box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} -- ok +_ = box.space._collation.index.name:delete{'test'} -- ok +opts.numeric_collation = 'PERL' +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} +opts.numeric_collation = 'ON' +_ = box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} --ok +_ = box.space._collation.index.name:delete{'test'} -- ok +opts.alternate_handling1 = 'ON' +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} +opts.alternate_handling1 = nil +opts.alternate_handling = 'ON' +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} +opts.alternate_handling = 'SHIFTED' +_ = box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} --ok +_ = box.space._collation.index.name:delete{'test'} -- ok +opts.case_first = 'ON' +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} +opts.case_first = 'OFF' +_ = box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} --ok +_ = box.space._collation.index.name:delete{'test'} -- ok +opts.case_level = 'UPPER' +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} +opts.case_level = 'DEFAULT' +_ = box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} --ok +_ = box.space._collation.index.name:delete{'test'} -- ok + +box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', setmap{}} +box.space._collation:select{} +test_run:cmd('restart server default') +box.space._collation:select{} +box.space._collation.index.name:delete{'test'} diff --git a/test/box/misc.result b/test/box/misc.result index f332fc156c796d3cd0b3ad503dbfbbd7b0255c7e..14c4c16f5465a4e521c2853617ac1ac58d8ce882 100644 --- a/test/box/misc.result +++ b/test/box/misc.result @@ -312,6 +312,7 @@ t; - 'box.error.FIELD_TYPE : 23' - 'box.error.WRONG_SPACE_FORMAT : 141' - 'box.error.UNKNOWN_UPDATE_OP : 28' + - 'box.error.WRONG_COLLATION_OPTIONS : 151' - 'box.error.CURSOR_NO_TRANSACTION : 80' - 'box.error.TUPLE_REF_OVERFLOW : 86' - 'box.error.ALTER_SEQUENCE : 143' diff --git a/test/wal_off/alter.result b/test/wal_off/alter.result index 7ac001ec0bd3da6906dd99abf906b5d170f0b094..c48294b3c8ef99544400384651eb38f7688ee02c 100644 --- a/test/wal_off/alter.result +++ b/test/wal_off/alter.result @@ -28,7 +28,7 @@ end; ... #spaces; --- -- 65517 +- 65515 ... -- cleanup for k, v in pairs(spaces) do diff --git a/test/xlog/upgrade.result b/test/xlog/upgrade.result index dd08961bc967963467b30a2c1131d7c22a160ace..d1cc579111eb52475791be0d65571bd1ebb770f0 100644 --- a/test/xlog/upgrade.result +++ b/test/xlog/upgrade.result @@ -41,6 +41,10 @@ box.space._schema:select() box.space._space:select() --- - - [272, 1, '_schema', 'memtx', 0, {}, [{'type': 'string', 'name': 'key'}]] + - [276, 1, '_collation', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, { + 'name': 'name', 'type': 'string'}, {'name': 'owner', 'type': 'unsigned'}, + {'name': 'type', 'type': 'string'}, {'name': 'locale', 'type': 'string'}, { + 'name': 'opts', 'type': 'map'}]] - [280, 1, '_space', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'owner', 'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'engine', 'type': 'string'}, {'name': 'field_count', 'type': 'unsigned'}, {'name': 'flags', @@ -93,6 +97,8 @@ box.space._space:select() box.space._index:select() --- - - [272, 0, 'primary', 'tree', {'unique': true}, [[0, 'string']]] + - [276, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]] + - [276, 1, 'name', 'tree', {'unique': true}, [[1, 'string']]] - [280, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]] - [280, 1, 'owner', 'tree', {'unique': false}, [[1, 'unsigned']]] - [280, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]] @@ -152,12 +158,18 @@ box.space._func:select() - [2, 4, 'somefunc', 1, 'LUA'] - [3, 1, 'someotherfunc', 0, 'LUA'] ... +box.space._collation:select() +--- +- - [0, 'unicode', 1, 'ICU', '', {}] + - [1, 'unicode_s1', 1, 'ICU', '', {'strength': 'primary'}] +... box.space._priv:select() --- - - [1, 0, 'role', 2, 4] - [1, 1, 'universe', 0, 7] - [1, 2, 'function', 1, 4] - [1, 2, 'function', 2, 4] + - [1, 2, 'space', 276, 2] - [1, 2, 'space', 281, 1] - [1, 2, 'space', 289, 1] - [1, 2, 'space', 297, 1] diff --git a/test/xlog/upgrade.test.lua b/test/xlog/upgrade.test.lua index c89e2cc0f753f10bbcb56cc7c8d9c8988d010fb6..0be2d34e95f543197069363614372c5a5d05c53d 100644 --- a/test/xlog/upgrade.test.lua +++ b/test/xlog/upgrade.test.lua @@ -25,6 +25,7 @@ box.space._space:select() box.space._index:select() box.space._user:select() box.space._func:select() +box.space._collation:select() box.space._priv:select() box.space._vspace ~= nil