Skip to content

Commit

Permalink
box: make functional index creation transactional
Browse files Browse the repository at this point in the history
The _func_index space trigger used to reject an insertion of a
tuple that defines an invalid functional index.
As insertion in _index space had been completed before, a garbage
is kept in _index space in such case.

We need to do something with the yelding _func_index trigger(that
rebuilds an index) to wrap all index creation operation in DDL
transaction in further patches (because only the first DDL
operation may yeld now).

This problem could be trivially solved with preparatory
initialization of index_def function ponter: the memtx_tree
index construction would perform all required job in such case.
Therefore the following index rebuild in _func_index trigger
becomes redundant and should be omitted.

In other words, a trivial prefetch operation makes possible
a transactional index creation (with space:create_index operation).

As for index construction during recovery (a lack of function
cache during recovery was the main motivation to introduce
_func_index space), it's workflow is kept unchanged.

Follow up #1250
Needed for #4348
Closes #4401
  • Loading branch information
kshcherbatov authored and locker committed Aug 6, 2019
1 parent 27436b4 commit 302bb32
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 18 deletions.
56 changes: 50 additions & 6 deletions src/box/alter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 =
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down
12 changes: 12 additions & 0 deletions src/box/index_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
96 changes: 87 additions & 9 deletions test/engine/func_index.result
Original file line number Diff line number Diff line change
Expand Up @@ -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'}}})
| ---
Expand Down Expand Up @@ -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()
| ---
| ...
44 changes: 41 additions & 3 deletions test/engine/func_index.test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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()

0 comments on commit 302bb32

Please sign in to comment.