diff --git a/src/box/alter.cc b/src/box/alter.cc index 92f1d5b221caa1da074746889535c71226fd2d61..4f2e34bf0d5a3003b5299bdc159e0237cd676d39 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -231,6 +231,23 @@ index_opts_decode(struct index_opts *opts, const char *map, } } +/** + * Helper routine for functional index function verification: + * only a deterministic persistent Lua function may be used in + * functional index for now. + */ +static void +func_index_check_func(struct func *func) { + assert(func != NULL); + if (func->def->language != FUNC_LANGUAGE_LUA || + func->def->body == NULL || !func->def->is_deterministic || + !func->def->is_sandboxed) { + tnt_raise(ClientError, ER_WRONG_INDEX_OPTIONS, 0, + "referenced function doesn't satisfy " + "functional index function constraints"); + } +} + /** * Create a index_def object from a record in _index * system space. @@ -285,7 +302,8 @@ index_def_new_from_tuple(struct tuple *tuple, struct space *space) space->def->fields, space->def->field_count, &fiber()->gc) != 0) diag_raise(); - key_def = key_def_new(part_def, part_count, opts.func_id > 0); + bool for_func_index = opts.func_id > 0; + key_def = key_def_new(part_def, part_count, for_func_index); if (key_def == NULL) diag_raise(); struct index_def *index_def = @@ -296,6 +314,26 @@ index_def_new_from_tuple(struct tuple *tuple, struct space *space) auto index_def_guard = make_scoped_guard([=] { index_def_delete(index_def); }); index_def_check_xc(index_def, space_name(space)); space_check_index_def_xc(space, index_def); + /* + * In case of functional index definition, resolve a + * function pointer to perform a complete index build + * (istead of initializing it in inactive state) in + * on_replace_dd_index trigger. This allows wrap index + * creation operation into transaction: only the first + * opperation in transaction is allowed to yeld. + * + * The initialisation during recovery is slightly + * different, because function cache is not initialized + * during _index space loading. Therefore the completion + * of a functional index creation is performed in + * _func_index space's trigger, via IndexRebuild + * operation. + */ + struct func *func = NULL; + if (for_func_index && (func = func_by_id(opts.func_id)) != NULL) { + func_index_check_func(func); + index_def_set_func(index_def, func); + } if (index_def->iid == 0 && space->sequence != NULL) index_def_check_sequence(index_def, space->sequence_fieldno, space->sequence_path, @@ -4725,12 +4763,11 @@ on_replace_dd_func_index(struct trigger *trigger, void *event) space = space_cache_find_xc(space_id); index = index_find_xc(space, index_id); func = func_cache_find(fid); - if (func->def->language != FUNC_LANGUAGE_LUA || - func->def->body == NULL || !func->def->is_deterministic || - !func->def->is_sandboxed) { + func_index_check_func(func); + if (index->def->opts.func_id != func->def->fid) { tnt_raise(ClientError, ER_WRONG_INDEX_OPTIONS, 0, - "referenced function doesn't satisfy " - "functional index function constraints"); + "Function ids defined in _index and " + "_func_index don't match"); } } else if (old_tuple != NULL && new_tuple == NULL) { uint32_t space_id = tuple_field_u32_xc(old_tuple, @@ -4746,6 +4783,13 @@ on_replace_dd_func_index(struct trigger *trigger, void *event) "functional index", "alter"); } + /** + * Index is already initialized for corresponding + * function. Index rebuild is not required. + */ + if (index_def_get_func(index->def) == func) + return; + alter = alter_space_new(space); auto scoped_guard = make_scoped_guard([=] {alter_space_delete(alter);}); alter_space_move_indexes(alter, 0, index->def->iid); diff --git a/src/box/index_def.h b/src/box/index_def.h index 62578bd607a7b2aed003d42afbbbaed1eeec4b4c..d928b23c7da45e812bb3cb5aab80028ccc397014 100644 --- a/src/box/index_def.h +++ b/src/box/index_def.h @@ -321,6 +321,18 @@ index_def_set_func(struct index_def *def, struct func *func) def->cmp_def->func_index_func = NULL; } +/** + * Get a func pointer by index definition. + * @param def Index def, containing key definitions. + * @returns not NULL function pointer when index definition + * refers to function and NULL otherwise. + */ +static inline struct func * +index_def_get_func(struct index_def *def) +{ + return def->key_def->func_index_func; +} + /** * Add an index definition to a list, preserving the * first position of the primary key. diff --git a/test/engine/func_index.result b/test/engine/func_index.result index 877b76d5ead679af769abf7c396e95af4aca5cf4..bb4200f7a6700dfea2d8115279dad9fd98f124aa 100644 --- a/test/engine/func_index.result +++ b/test/engine/func_index.result @@ -58,27 +58,18 @@ _ = s:create_index('idx', {func = box.func.s_nonpersistent.id, parts = {{1, 'uns | - error: 'Wrong index options (field 0): referenced function doesn''t satisfy functional | index function constraints' | ... -s.index.idx:drop() - | --- - | ... -- Can't use non-deterministic function in functional index. _ = s:create_index('idx', {func = box.func.s_ivaliddef1.id, parts = {{1, 'unsigned'}}}) | --- | - error: 'Wrong index options (field 0): referenced function doesn''t satisfy functional | index function constraints' | ... -s.index.idx:drop() - | --- - | ... -- Can't use non-sandboxed function in functional index. _ = s:create_index('idx', {func = box.func.s_ivaliddef2.id, parts = {{1, 'unsigned'}}}) | --- | - error: 'Wrong index options (field 0): referenced function doesn''t satisfy functional | index function constraints' | ... -s.index.idx:drop() - | --- - | ... -- Can't use non-sequential parts in returned key definition. _ = s:create_index('idx', {func = box.func.ss.id, parts = {{1, 'unsigned'}, {3, 'unsigned'}}}) | --- @@ -731,3 +722,90 @@ box.schema.func.drop('s') box.schema.func.drop('sub') | --- | ... + +-- +-- gh-4401: make functional index creation transactional +-- +test_run:cmd("setopt delimiter ';'") + | --- + | - true + | ... +function test1() + lua_code = [[function(tuple) return {tuple[1] + tuple[2]} end]] + box.schema.func.create('extr', {body = lua_code, is_deterministic = true, is_sandboxed = true}) + box.schema.func.create('extr1', {body = lua_code, is_deterministic = true, is_sandboxed = true}) + s = box.schema.space.create('withdata') + pk = s:create_index('pk') + box.space._index:insert({s.id, 2, 'idx', 'tree', {unique=true, func=box.func.extr.id}, {{0, 'integer'}}}) + box.space._func_index:insert({s.id, 2, box.func.extr1.id}) +end +test_run:cmd("setopt delimiter ''"); + | --- + | ... + +box.atomic(test1) + | --- + | - error: 'Wrong index options (field 0): Function ids defined in _index and _func_index + | don''t match' + | ... + +box.func.extr1 == nil + | --- + | - true + | ... +box.func.extr == nil + | --- + | - true + | ... +box.is_in_txn() + | --- + | - false + | ... +box.space._space.index.name:count('withdata') == 0 + | --- + | - true + | ... + +-- Test successful index creation +s = box.schema.space.create('withdata', {engine = engine}) + | --- + | ... +lua_code = [[function(tuple) return {tuple[1] + tuple[2]} end]] + | --- + | ... +box.schema.func.create('extr', {body = lua_code, is_deterministic = true, is_sandboxed = true}) + | --- + | ... +pk = s:create_index('pk') + | --- + | ... +test_run:cmd("setopt delimiter ';'") + | --- + | - true + | ... +function test2() + idx = s:create_index('idx', {unique = true, func = 'extr', parts = {{1, 'integer'}}}) +end +test_run:cmd("setopt delimiter ''"); + | --- + | ... + +box.atomic(test2) + | --- + | ... + +s:insert({1, 2}) + | --- + | - [1, 2] + | ... +idx:get({3}) + | --- + | - [1, 2] + | ... + +s:drop() + | --- + | ... +box.func.extr:drop() + | --- + | ... diff --git a/test/engine/func_index.test.lua b/test/engine/func_index.test.lua index 372ec800d2547945568d1598f99b6b0e008877fc..f31162c97c333452c79185251ba1d333886868a7 100644 --- a/test/engine/func_index.test.lua +++ b/test/engine/func_index.test.lua @@ -22,13 +22,10 @@ _ = s:create_index('idx', {func = 6666, parts = {{1, 'unsigned'}}}) s.index.idx:drop() -- Can't use non-persistent function in functional index. _ = s:create_index('idx', {func = box.func.s_nonpersistent.id, parts = {{1, 'unsigned'}}}) -s.index.idx:drop() -- Can't use non-deterministic function in functional index. _ = s:create_index('idx', {func = box.func.s_ivaliddef1.id, parts = {{1, 'unsigned'}}}) -s.index.idx:drop() -- Can't use non-sandboxed function in functional index. _ = s:create_index('idx', {func = box.func.s_ivaliddef2.id, parts = {{1, 'unsigned'}}}) -s.index.idx:drop() -- Can't use non-sequential parts in returned key definition. _ = s:create_index('idx', {func = box.func.ss.id, parts = {{1, 'unsigned'}, {3, 'unsigned'}}}) -- Can't use parts started not by 1 field. @@ -248,3 +245,44 @@ idx2:get(3) s:drop() box.schema.func.drop('s') box.schema.func.drop('sub') + +-- +-- gh-4401: make functional index creation transactional +-- +test_run:cmd("setopt delimiter ';'") +function test1() + lua_code = [[function(tuple) return {tuple[1] + tuple[2]} end]] + box.schema.func.create('extr', {body = lua_code, is_deterministic = true, is_sandboxed = true}) + box.schema.func.create('extr1', {body = lua_code, is_deterministic = true, is_sandboxed = true}) + s = box.schema.space.create('withdata') + pk = s:create_index('pk') + box.space._index:insert({s.id, 2, 'idx', 'tree', {unique=true, func=box.func.extr.id}, {{0, 'integer'}}}) + box.space._func_index:insert({s.id, 2, box.func.extr1.id}) +end +test_run:cmd("setopt delimiter ''"); + +box.atomic(test1) + +box.func.extr1 == nil +box.func.extr == nil +box.is_in_txn() +box.space._space.index.name:count('withdata') == 0 + +-- Test successful index creation +s = box.schema.space.create('withdata', {engine = engine}) +lua_code = [[function(tuple) return {tuple[1] + tuple[2]} end]] +box.schema.func.create('extr', {body = lua_code, is_deterministic = true, is_sandboxed = true}) +pk = s:create_index('pk') +test_run:cmd("setopt delimiter ';'") +function test2() + idx = s:create_index('idx', {unique = true, func = 'extr', parts = {{1, 'integer'}}}) +end +test_run:cmd("setopt delimiter ''"); + +box.atomic(test2) + +s:insert({1, 2}) +idx:get({3}) + +s:drop() +box.func.extr:drop()