From 088d45eb181578f882b4e30b3d197523cbdb28a6 Mon Sep 17 00:00:00 2001 From: Ilya Verbin <iverbin@tarantool.org> Date: Wed, 5 Jul 2023 20:26:31 +0300 Subject: [PATCH] box: reference collation by key_def parts It was possible to delete a collation, which is in use by a key_def. Part of #4544 NO_DOC=bugfix NO_CHANGELOG=next commit (cherry picked from commit 07beb340a9eb6a9f38699836cfce5002e807466e) --- src/box/index_def.c | 13 ++-- src/box/key_def.c | 9 +++ src/box/lua/key_def.c | 15 ++++- .../gh_4544_collation_drop_test.lua | 62 +++++++++++++++++++ 4 files changed, 88 insertions(+), 11 deletions(-) create mode 100644 test/engine-luatest/gh_4544_collation_drop_test.lua diff --git a/src/box/index_def.c b/src/box/index_def.c index 5dbc5d8e98..458ebcaecd 100644 --- a/src/box/index_def.c +++ b/src/box/index_def.c @@ -204,22 +204,17 @@ index_stat_dup(const struct index_stat *src) return dup; } -/** Free a key definition. */ void index_def_delete(struct index_def *index_def) { index_opts_destroy(&index_def->opts); free(index_def->name); - if (index_def->key_def) { - TRASH(index_def->key_def); - free(index_def->key_def); - } + if (index_def->key_def) + key_def_delete(index_def->key_def); - if (index_def->cmp_def) { - TRASH(index_def->cmp_def); - free(index_def->cmp_def); - } + if (index_def->cmp_def) + key_def_delete(index_def->cmp_def); TRASH(index_def); free(index_def); diff --git a/src/box/key_def.c b/src/box/key_def.c index 0de7edc438..69386321b7 100644 --- a/src/box/key_def.c +++ b/src/box/key_def.c @@ -121,6 +121,9 @@ key_def_copy_impl(struct key_def *res, const struct key_def *src, size_t sz) size_t path_offset = src->multikey_path - (char *)src; res->multikey_path = (char *)res + path_offset; } + for (uint32_t i = 0; i < res->part_count; i++) + if (res->parts[i].coll != NULL) + coll_ref(res->parts[i].coll); return res; } @@ -144,6 +147,10 @@ key_def_dup(const struct key_def *src) void key_def_delete(struct key_def *def) { + for (uint32_t i = 0; i < def->part_count; i++) + if (def->parts[i].coll != NULL) + coll_unref(def->parts[i].coll); + TRASH(def); free(def); } @@ -247,6 +254,8 @@ key_def_set_part(struct key_def *def, uint32_t part_no, uint32_t fieldno, def->parts[part_no].fieldno = fieldno; def->parts[part_no].type = type; def->parts[part_no].coll = coll; + if (coll != NULL) + coll_ref(def->parts[part_no].coll); def->parts[part_no].coll_id = coll_id; def->parts[part_no].sort_order = sort_order; def->parts[part_no].offset_slot_cache = offset_slot; diff --git a/src/box/lua/key_def.c b/src/box/lua/key_def.c index e5fc370e49..375d18f227 100644 --- a/src/box/lua/key_def.c +++ b/src/box/lua/key_def.c @@ -68,9 +68,20 @@ luaT_push_key_def(struct lua_State *L, const struct key_def *key_def) } if (part->coll_id != COLL_NONE) { + const char *name; struct coll_id *coll_id = coll_by_id(part->coll_id); - assert(coll_id != NULL); - lua_pushstring(L, coll_id->name); + /* + * It is possible that the original collation with + * `id == part->coll_id` was replaced by the new one + * with the same id, but a different fingerprint. + * However, it their fingerprints match, then coll_new() + * reuses `struct coll` and this condition is met: + */ + if (coll_id != NULL && coll_id->coll == part->coll) + name = coll_id->name; + else + name = "<deleted>"; + lua_pushstring(L, name); lua_setfield(L, -2, "collation"); } lua_rawseti(L, -2, i + 1); diff --git a/test/engine-luatest/gh_4544_collation_drop_test.lua b/test/engine-luatest/gh_4544_collation_drop_test.lua new file mode 100644 index 0000000000..4f4f532405 --- /dev/null +++ b/test/engine-luatest/gh_4544_collation_drop_test.lua @@ -0,0 +1,62 @@ +local t = require('luatest') + +local function before_all(cg) + local server = require('luatest.server') + cg.server = server:new() + cg.server:start() +end + +local function after_all(cg) + cg.server:drop() +end + +local g1 = t.group('gh-4544-1') +g1.before_all(before_all) +g1.after_all(after_all) + +-- Check that key_def compare doesn't crash after collation drop. +g1.test_keydef_crash = function(cg) + cg.server:exec(function() + local key_def = require('key_def') + local coll_name = 'unicode_af_s1' + local kd = key_def.new({{field = 1, type = 'string', + collation = coll_name}}) + box.internal.collation.drop(coll_name) + kd:compare({'a'}, {'b'}) + t.assert_equals(kd:totable()[1].collation, '<deleted>') + end) +end + +-- Replace old collation, which is used by key_def, by the new one with the same +-- fingerprint. +g1.test_keydef_replace_coll_same = function(cg) + cg.server:exec(function() + local key_def = require('key_def') + local old_name = 'my old coll' + local new_name = 'my new coll' + box.internal.collation.create(old_name, 'ICU', '', {}) + local kd = key_def.new({{field = 1, type = 'string', + collation = old_name}}) + box.internal.collation.drop(old_name) + box.internal.collation.create(new_name, 'ICU', '', {}) + t.assert_equals(kd:totable()[1].collation, new_name) + box.internal.collation.drop(new_name) + end) +end + +-- Replace old collation, which is used by key_def, by the new one with +-- different fingerprint. +g1.test_keydef_replace_coll_different = function(cg) + cg.server:exec(function() + local key_def = require('key_def') + local old_name = 'my old coll' + local new_name = 'my new coll' + box.internal.collation.create(old_name, 'ICU', '', {}) + local kd = key_def.new({{field = 1, type = 'string', + collation = old_name}}) + box.internal.collation.drop(old_name) + box.internal.collation.create(new_name, 'ICU', 'ru-RU', {}) + t.assert_equals(kd:totable()[1].collation, '<deleted>') + box.internal.collation.drop(new_name) + end) +end -- GitLab