* [tarantool-patches] Re: [PATCH v1 2/2] box: functional and multikey indexes
[not found] ` <45d55e70c60c5c4b5806c8e7ce3330957a8dc0ce.1542460247.git.kshcherbatov@tarantool.org>
@ 2018-11-20 12:18 ` Kirill Shcherbatov
0 siblings, 0 replies; 5+ messages in thread
From: Kirill Shcherbatov @ 2018-11-20 12:18 UTC (permalink / raw)
To: tarantool-patches, Georgy Kirichenko
53,6 +55,22 @@ box_lua_call(struct call_request *request, struct port *port);
int
box_lua_eval(struct call_request *request, struct port *port);
+/** Set functional index trap trigger on space. */
+void
+lua_func_idx_trigger_set(const char *space_name, const char *idx_name,
+ int32_t *trigger_ref);
+
+/**
+ * Make and register a new LUA function object by func_code
+ * string.
+ */
+int
+lua_func_new(const char *func_code, int32_t *func_ref);
+
+/** Release registered LUA function object. */
+void
+lua_func_delete(uint32_t func_ref);
+
#if defined(__cplusplus)
} /* extern "C" */
#endif /* defined(__cplusplus) */
diff --git a/src/box/lua/func_idx.lua b/src/box/lua/func_idx.lua
new file mode 100644
index 0000000..8ffc7a1
--- /dev/null
+++ b/src/box/lua/func_idx.lua
@@ -0,0 +1,347 @@
+-- func_idx.lua (internal file)
+--
+
+local function func_idx_space_name(space_name, idx_name)
+ return '_i_' .. idx_name .. '_' .. space_name
+end
+
+local function test_a_in_b(a, b)
+ local test = {}
+ for _, v in pairs(b) do
+ test[v] = true
+ end
+ for _, v in pairs(a) do
+ if test[v] == nil then return false end
+ end
+ return true
+end
+
+local function func_idx_keys_equal(a, b)
+ return test_a_in_b(a, b) and test_a_in_b(b, a)
+end
+
+local function func_idx_key_in_set(key, set)
+ for _, r in pairs(set) do
+ if func_idx_keys_equal(key, r) then return true end
+ end
+ return false
+end
+
+local function func_idx_select_do(index, key, iterator, func, arg)
+ local space = box.space[index.space_id]
+ local ispace_name = func_idx_space_name(space.name, index.name)
+ local ispace = box.space[ispace_name]
+ local pkey_offset = index.functional.pkey_offset
+ local unique_filter = {}
+ for _, k in ispace:pairs(key, {iterator = iterator}) do
+ local pkey = {}
+ for _, p in pairs(space.index[0].parts) do
+ table.insert(pkey, k[#pkey + pkey_offset + 1])
+ end
+ -- test tuple uniqueness for multikey index
+ if (index.is_multikey == false) or
+ (func_idx_key_in_set(pkey, unique_filter) == false) then
+ table.insert(unique_filter, pkey)
+ if func(arg, space, pkey) ~= 0 then break end
+ end
+ end
+end
+
+local function func_idx_select(index, key, opts)
+ box.internal.check_index_arg(index, 'select')
+ key = key or {}
+ local iterator, offset, limit =
+ box.internal.check_select_opts(opts, #key < index.functional.pkey_offset)
+ local res = {}
+ local curr_offset, done = 0, 0
+ func_idx_select_do(index, key, iterator,
+ function(container, space, pkey)
+ if done >= limit then return 1 end
+ if curr_offset >= offset then
+ local tuple = space:get(pkey)
+ table.insert(container, tuple)
+ done = done + 1
+ end
+ curr_offset = curr_offset + 1
+ return 0
+ end, res)
+ return res
+end
+
+local function func_idx_get(index, key)
+ box.internal.check_index_arg(index, 'get')
+ if not index.unique then
+ box.error(box.error.MORE_THAN_ONE_TUPLE, "")
+ end
+ ret = func_idx_select(index, key, {limit = 1})[1]
+ if ret ~= nil then
+ return ret
+ end
+end
+
+local function func_idx_bsize(index)
+ box.internal.check_index_arg(index, 'bsize')
+ local space = box.space[index.space_id]
+ local ispace = box.space[func_idx_space_name(space.name, index.name)]
+ local ispace_pk = ispace.index[0]
+ return ispace_pk:bsize()
+end
+
+local function func_idx_random(index, seed)
+ box.internal.check_index_arg(index, 'random')
+ local space = box.space[index.space_id]
+ return space.index[0]:random(seed)
+end
+
+local function func_idx_count(index, key, opts)
+ box.internal.check_index_arg(index, 'count')
+ key = key or {}
+ local iterator, offset, limit =
+ box.internal.check_select_opts(opts, #key < index.functional.pkey_offset)
+ local res = {count = 0}
+ func_idx_select_do(index, key, iterator,
+ function(container, space, pkey)
+ container.count = container.count + 1
+ return 0
+ end, res)
+ return res.count
+end
+
+local function func_idx_rename(index, name)
+ box.internal.check_index_arg(index, 'rename')
+ local space = box.space[index.space_id]
+ local ispace = box.space[func_idx_space_name(space.name, index.name)]
+ local new_ispace_name = func_idx_space_name(space.name, name)
+ ispace:rename(new_ispace_name)
+ return box.schema.index.rename(space.id, index.id, name)
+end
+
+local function func_idx_min(index, key)
+ box.internal.check_index_arg(index, 'min')
+ return func_idx_select(index, key, {limit = 1, iterator = 'EQ'})
+end
+
+local function func_idx_max(index, key)
+ box.internal.check_index_arg(index, 'max')
+ return func_idx_select(index, key, {limit = 1, iterator = 'REQ'})
+end
+
+local function func_idx_drop(index, key)
+ box.internal.check_index_arg(index, 'drop')
+ local space = box.space[index.space_id]
+ space:on_replace(nil, index.functional.trigger)
+ local ispace = box.space[func_idx_space_name(space.name, index.name)]
+ ispace:drop()
+ return box.schema.index.drop(space.id, index.id)
+end
+
+local function func_idx_delete(index, key)
+ box.internal.check_index_arg(index, 'delete')
+ if not index.unique then
+ box.error(box.error.MORE_THAN_ONE_TUPLE, "")
+ end
+ key = key or {}
+ local iterator, offset, limit = box.internal.check_select_opts({}, false)
+ local ret = {}
+ func_idx_select_do(index, key, iterator,
+ function(container, space, pkey)
+ local tuple = space:delete(pkey)
+ if tuple ~= nil then
+ table.insert(container, tuple)
+ end
+ return 1
+ end, ret)
+ if ret[1] ~= nil then
+ return ret[1]
+ end
+end
+
+local function func_idx_update(index, key, ops)
+ box.internal.check_index_arg(index, 'update')
+ if not index.unique then
+ box.error(box.error.MORE_THAN_ONE_TUPLE, "")
+ end
+ local iterator, offset, limit = box.internal.check_select_opts({}, false)
+ local ret = {}
+ func_idx_select_do(index, key, iterator,
+ function(container, space, pkey)
+ local tuple = space:update(pkey, ops)
+ if tuple ~= nil then
+ table.insert(container, tuple)
+ end
+ return 1
+ end, ret)
+ if ret[1] ~= nil then
+ return ret[1]
+ end
+end
+
+local function func_idx_pairs_next(param, state)
+ local state, tuple = param.gen(param.gen_param, state)
+ if state == nil then
+ param.filter_set = {}
+ return nil
+ else
+ local pkey_offset = param.index.functional.pkey_offset
+ local pkey = {}
+ for _, p in pairs(param.space.index[0].parts) do
+ table.insert(pkey, tuple[#pkey + pkey_offset + 1])
+ end
+ return state, param.space:get(pkey)
+ end
+end
+
+local function func_idx_pairs(index, key, opts)
+ box.internal.check_index_arg(index, 'pairs')
+ local space = box.space[index.space_id]
+ local ispace_name = func_idx_space_name(space.name, index.name)
+ local ispace = box.space[ispace_name]
+ key = key or {}
+ local iterator, offset, limit =
+ box.internal.check_select_opts(opts, #key < index.functional.pkey_offset)
+ local obj, param, state = ispace:pairs(key, {iterator = iterator})
+ param = {filter_set = {}, gen_param = param, gen = obj.gen,
+ index = index, space = space}
+ obj.gen = function(param, state)
+ local state, tuple = func_idx_pairs_next(param, state)
+ while state ~= nil and func_idx_key_in_set(tuple, param.filter_set) == true do
+ state, tuple = func_idx_pairs_next(param, state)
+ end
+ if state == nil then
+ param.filter_set = {}
+ return nil
+ else
+ table.insert(param.filter_set, tuple)
+ return state, tuple
+ end
+ end
+ return obj, param, state
+end
+
+local function func_idx_unique_filter(keys)
+ local ret = {}
+ for _, k in pairs(keys) do
+ if func_idx_key_in_set(k, ret) then goto continue end
+ table.insert(ret, k)
+ ::continue::
+ end
+ return ret
+end
+
+local function func_idx_fkeys(func, tuple, is_multikey)
+ local fkeys = func(tuple)
+ if is_multikey then
+ fkeys = func_idx_unique_filter(fkeys)
+ else
+ fkeys = {{fkeys}}
+ end
+ return fkeys
+end
+
+local func_idx = {}
+
+func_idx.space_trigger_set = function (space_name, idx_name)
+ local space = box.space[space_name]
+ assert(space ~= nil)
+ local ispace = box.space[func_idx_space_name(space_name, idx_name)]
+ assert(ispace ~= nil)
+ local trigger = function (old, new)
+ if box.info.status ~= 'running' then
+ return
+ end
+ local findex = space.index[idx_name]
+ local func = findex.functional.func
+ local pkey = {}
+ local tuple = old or new
+ for _, p in pairs(space.index[0].parts) do
+ table.insert(pkey, tuple[p.fieldno])
+ end
+ if old ~= nil then
+ local fkeys = func_idx_fkeys(func, old, findex.is_multikey)
+ for _, key in pairs(fkeys) do
+ if findex.unique == false then
+ for _, v in pairs(pkey) do
+ table.insert(key, v)
+ end
+ end
+ ispace:delete(key)
+ end
+ end
+ if new ~= nil then
+ local fkeys = func_idx_fkeys(func, new, findex.is_multikey)
+ for _, key in pairs(fkeys) do
+ for _, v in pairs(pkey) do
+ table.insert(key, v)
+ end
+ ispace:insert(key)
+ end
+ end
+ end
+ space:on_replace(trigger)
+ return trigger
+end
+
+func_idx.ispace_create = function (space, name, func_code, func_format,
+ is_unique, is_multikey)
+ local func, err = loadstring(func_code)
+ if func == nil then
+ box.error(box.error.ILLEGAL_PARAMS,
+ "functional index extractor routine is invalid: "..err)
+ end
+ func = func()
+ local iformat = {}
+ local iparts = {}
+ for _, f in pairs(func_format) do
+ table.insert(iformat, {name = 'i' .. tostring(#iformat), type = f.type})
+ table.insert(iparts, {#iparts + 1, f.type,
+ is_nullable = f.is_nullable,
+ collation = f.collation})
+ end
+ local pkey_offset = #func_format
+ local pkey = box.internal.check_primary_index(space)
+ for _, p in pairs(pkey.parts) do
+ table.insert(iformat, {name = 'i' .. tostring(#iformat), type = p.type})
+ if is_unique == false then
+ table.insert(iparts, {#iparts + 1, p.type})
+ end
+ end
+ local ispace = box.schema.space.create('_i_' .. name .. '_' .. space.name,
+ {engine = space.engine})
+ ispace:format(iformat)
+ ispace:create_index('pk', {parts = iparts})
+
+ for _, tuple in space:pairs() do
+ local pkey = {}
+ for _, p in pairs(space.index[0].parts) do
+ table.insert(pkey, tuple[p.fieldno])
+ end
+ local fkeys = func_idx_fkeys(func, tuple, is_multikey)
+ for _, key in pairs(fkeys) do
+ for _, v in pairs(pkey) do
+ table.insert(key, v)
+ end
+ ispace:insert(key)
+ end
+ end
+end
+
+func_idx.monkeypatch = function (index)
+ local meta = getmetatable(index)
+ meta.select = func_idx_select
+ meta.get = func_idx_get
+ meta.bsize = func_idx_bsize
+ meta.random = func_idx_random
+ meta.count = func_idx_count
+ meta.rename = func_idx_rename
+ meta.min = func_idx_min
+ meta.max = func_idx_max
+ meta.drop = func_idx_drop
+ meta.delete = func_idx_delete
+ meta.update = func_idx_update
+ meta.pairs = func_idx_pairs
+ meta.alter = nil
+ meta.compact = nil
+ meta.stat = nil
+end
+
+return func_idx
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index b6693b1..93f7a6c 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -7,6 +7,7 @@ local fun = require('fun')
local log = require('log')
local fio = require('fio')
local json = require('json')
+local func_idx = require('func_idx')
local session = box.session
local internal = require('box.internal')
local function setmap(table)
@@ -281,6 +282,10 @@ local function check_param_table(table, template)
end
end
+function func_idx_trigger_set(space_name, idx_name)
+ return func_idx.space_trigger_set(space_name, idx_name)
+end
+
--[[
@brief Common function to check type parameter (of function)
Calls box.error(box.error.ILLEGAL_PARAMS, ) on error
@@ -700,6 +705,9 @@ local index_options = {
range_size = 'number',
page_size = 'number',
bloom_fpr = 'number',
+ is_multikey = 'boolean',
+ func_code = 'string',
+ func_format = 'table'
}
--
@@ -745,16 +753,31 @@ box.schema.index.create = function(space_id, name, options)
}
options_defaults = type_dependent_defaults[options.type]
or type_dependent_defaults.other
- if not options.parts then
- local fieldno = options_defaults.parts[1]
- if #format >= fieldno then
- local t = format[fieldno].type
- if t ~= 'any' then
- options.parts = {{fieldno, format[fieldno].type}}
+ if not options.func_code then
+ if not options.parts then
+ local fieldno = options_defaults.parts[1]
+ if #format >= fieldno then
+ local t = format[fieldno].type
+ if t ~= 'any' then
+ options.parts = {{fieldno, format[fieldno].type}}
+ end
end
end
+ options = update_param_table(options, options_defaults)
+ elseif options.parts ~= nil then
+ box.error(box.error.ILLEGAL_PARAMS,
+ "options.parts: parts can't be set for functional index")
+ end
+ if (options.func_code == nil) ~= (options.func_format == nil) then
+ box.error(box.error.ILLEGAL_PARAMS,
+ "options.func_: bouth of parameters func_code and "..
+ "func_format should be specified for functional index")
+ end
+ if options.is_multikey and (options.func_code == nil) then
+ box.error(box.error.ILLEGAL_PARAMS,
+ "options.is_multikey: can't specify is_multikey option for "..
+ "non-functional index")
end
- options = update_param_table(options, options_defaults)
if space.engine == 'vinyl' then
options_defaults = {
page_size = box.cfg.vinyl_page_size,
@@ -792,8 +815,11 @@ box.schema.index.create = function(space_id, name, options)
end
end
end
- local parts, parts_can_be_simplified =
- update_index_parts(format, options.parts)
+ local parts, parts_can_be_simplified = {}, false
+ if not options.func_code then
+ parts, parts_can_be_simplified =
+ update_index_parts(format, options.parts)
+ end
-- create_index() options contains type, parts, etc,
-- stored separately. Remove these members from index_opts
local index_opts = {
@@ -805,6 +831,9 @@ box.schema.index.create = function(space_id, name, options)
run_count_per_level = options.run_count_per_level,
run_size_ratio = options.run_size_ratio,
bloom_fpr = options.bloom_fpr,
+ is_multikey = options.is_multikey,
+ func_code = options.func_code,
+ func_format = options.func_format,
}
local field_type_aliases = {
num = 'unsigned'; -- Deprecated since 1.7.2
@@ -849,6 +878,11 @@ box.schema.index.create = function(space_id, name, options)
if parts_can_be_simplified then
parts = simplify_index_parts(parts)
end
+ if options.func_code ~= nil then
+ func_idx.ispace_create(space, name, options.func_code,
+ options.func_format, options.unique or false,
+ options.is_multikey or false)
+ end
_index:insert{space_id, iid, name, options.type, index_opts, parts}
if sequence ~= nil then
_space_sequence:insert{space_id, sequence, sequence_is_generated}
@@ -1319,6 +1353,7 @@ local function check_select_opts(opts, key_is_nil)
end
return iterator, offset, limit
end
+box.internal.check_select_opts = check_select_opts
base_index_mt.select_ffi = function(index, key, opts)
check_index_arg(index, 'select')
@@ -1546,6 +1581,7 @@ local function wrap_schema_object_mt(name)
return mt
end
+
function box.schema.space.bless(space)
local index_mt_name
if space.engine == 'vinyl' then
@@ -1560,6 +1596,9 @@ function box.schema.space.bless(space)
for j, index in pairs(space.index) do
if type(j) == 'number' then
setmetatable(index, wrap_schema_object_mt(index_mt_name))
+ if index.functional ~= nil then
+ func_idx.monkeypatch(index)
+ end
end
end
end
diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
index 7cae436..227dd20 100644
--- a/src/box/lua/space.cc
+++ b/src/box/lua/space.cc
@@ -312,6 +312,30 @@ lbox_fillspace(struct lua_State *L, struct space *space, int i)
lua_settable(L, -3); /* space.index[k].parts */
+ if (index_is_functional(index_def)) {
+ lua_pushstring(L, "functional");
+ lua_newtable(L);
+
+ lua_pushstring(L, index_def->opts.func_code);
+ lua_setfield(L, -2, "func_code");
+
+ lua_pushinteger(L, index_def->opts.pkey_offset);
+ lua_setfield(L, -2, "pkey_offset");
+
+ lua_rawgeti(L, LUA_REGISTRYINDEX, index->func_ref);
+ assert(lua_isfunction(L, -1));
+ lua_setfield(L, -2, "func");
+
+ lua_rawgeti(L, LUA_REGISTRYINDEX, index->func_trigger_ref);
+ assert(lua_isfunction(L, -1));
+ lua_setfield(L, -2, "trigger");
+
+ lua_settable(L, -3);
+ }
+
+ lua_pushboolean(L, index_opts->is_multikey);
+ lua_setfield(L, -2, "is_multikey");
+
lua_pushstring(L, "sequence_id");
if (k == 0 && space->sequence != NULL) {
lua_pushnumber(L, space->sequence->def->id);
diff --git a/src/box/memtx_engine.c b/src/box/memtx_engine.c
index 6a91d0b..6369c02 100644
--- a/src/box/memtx_engine.c
+++ b/src/box/memtx_engine.c
@@ -1293,6 +1293,12 @@ memtx_index_def_change_requires_rebuild(struct index *index,
if (!old_def->opts.is_unique && new_def->opts.is_unique)
return true;
+ if (index_is_functional(old_def) != index_is_functional(new_def))
+ return true;
+ else if (index_is_functional(old_def) &&
+ strcmp(old_def->opts.func_code, new_def->opts.func_code) != 0)
+ return true;
+
const struct key_def *old_cmp_def, *new_cmp_def;
if (index_depends_on_pk(index)) {
old_cmp_def = old_def->cmp_def;
diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index b09b6ad..7aa370d 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -955,6 +955,12 @@ vinyl_index_def_change_requires_rebuild(struct index *index,
if (!old_def->opts.is_unique && new_def->opts.is_unique)
return true;
+ if (index_is_functional(old_def) != index_is_functional(new_def))
+ return true;
+ else if (index_is_functional(old_def) &&
+ strcmp(old_def->opts.func_code, new_def->opts.func_code) != 0)
+ return true;
+
assert(index_depends_on_pk(index));
const struct key_def *old_cmp_def = old_def->cmp_def;
const struct key_def *new_cmp_def = new_def->cmp_def;
diff --git a/src/lua/init.c b/src/lua/init.c
index 3728a57..3d58876 100644
--- a/src/lua/init.c
+++ b/src/lua/init.c
@@ -81,6 +81,7 @@ bool start_loop = true;
extern char strict_lua[],
uuid_lua[],
msgpackffi_lua[],
+ func_idx_lua[],
fun_lua[],
crypto_lua[],
digest_lua[],
@@ -120,6 +121,7 @@ extern char strict_lua[],
static const char *lua_modules[] = {
/* Make it first to affect load of all other modules */
"strict", strict_lua,
+ "func_idx", func_idx_lua,
"fun", fun_lua,
"tarantool", init_lua,
"errno", errno_lua,
diff --git a/test/engine/functional_idx.result b/test/engine/functional_idx.result
new file mode 100644
index 0000000..9bd3164
--- /dev/null
+++ b/test/engine/functional_idx.result
@@ -0,0 +1,120 @@
+msgpack = require('msgpack')
+---
+...
+env = require('test_run')
+---
+...
+test_run = env.new()
+---
+...
+engine = test_run:get_cfg('engine')
+---
+...
+format = {{'id','unsigned'}, {'name','string'},{'address', 'string'}}
+---
+...
+s = box.schema.space.create('clients', {engine = engine, format=format})
+---
+...
+addr_extractor = [[return function(tuple) if type(tuple.address) ~= 'string' then return nil, 'Invalid field type' end local t = tuple.address:lower():split() for k,v in pairs(t) do t[k] = {v} end return t end]]
+---
+...
+addr_extractor_format = {{type='str', collation='unicode_ci'}}
+---
+...
+s:create_index('address', {func_code = addr_extractor, func_format = addr_extractor_format, unique=true})
+---
+- error: 'No index #0 is defined in space ''clients'''
+...
+pk = s:create_index('pk')
+---
+...
+s:insert({1, 'Vasya Pupkin', 'Russia Moscow Dolgoprudny Moscow Street 9А'})
+---
+- [1, 'Vasya Pupkin', 'Russia Moscow Dolgoprudny Moscow Street 9А']
+...
+s:create_index('address', {func_code = addr_extractor, unique=true})
+---
+- error: 'Illegal parameters, options.func_: bouth of parameters func_code and func_format
+ should be specified for functional index'
+...
+s:create_index('address', {func_format = addr_extractor_format, unique=true})
+---
+- error: 'Illegal parameters, options.func_: bouth of parameters func_code and func_format
+ should be specified for functional index'
+...
+s:create_index('address', {func_code = addr_extractor..'err', func_format = addr_extractor_format, unique=true})
+---
+- error: 'Illegal parameters, functional index extractor routine is invalid: [string
+ "return function(tuple) if type(tuple.address)..."]:1: ''end'' expected near ''enderr'''
+...
+addr_low = s:create_index('address', {func_code = addr_extractor, func_format = addr_extractor_format, unique=true, is_multikey=true})
+---
+...
+assert(box.space.clients:on_replace()[1] == addr_low.functional.trigger)
+---
+- true
+...
+s:insert({2, 'James Bond', 'GB London Mi6'})
+---
+- [2, 'James Bond', 'GB London Mi6']
+...
+s:insert({3, 'Kirill Alekseevich', 'Russia Moscow Dolgoprudny RocketBuilders 1'})
+---
+- error: Duplicate key exists in unique index 'pk' in space '_i_address_clients'
+...
+s:insert({4, 'Jack London', 'GB'})
+---
+- error: Duplicate key exists in unique index 'pk' in space '_i_address_clients'
+...
+addr_low:count()
+---
+- 2
+...
+addr_low:count({'moscow'})
+---
+- 1
+...
+addr_low:count({'gb'})
+---
+- 1
+...
+addr_extractor = [[return function(tuple) if type(tuple.address) ~= 'string' then return nil, 'Invalid field type' end return tuple.address:upper():split()[1] end]]
+---
+...
+addr_high = s:create_index('address2', {func_code = addr_extractor, func_format = addr_extractor_format, unique=true, is_multikey=false})
+---
+...
+addr_high:select()
+---
+- - [2, 'James Bond', 'GB London Mi6']
+ - [1, 'Vasya Pupkin', 'Russia Moscow Dolgoprudny Moscow Street 9А']
+...
+addr_high:select({}, {limit = 1})
+---
+- - [2, 'James Bond', 'GB London Mi6']
+...
+addr_high:select({}, {limit = 1, offset=1})
+---
+- - [1, 'Vasya Pupkin', 'Russia Moscow Dolgoprudny Moscow Street 9А']
+...
+addr_high:select({'Moscow'})
+---
+- []
+...
+for _, v in addr_low:pairs() do print(v.name) end
+---
+...
+_ = addr_low:delete({'moscow'})
+---
+...
+addr_high:select({'MOSCOW'})
+---
+- []
+...
+addr_high:drop()
+---
+...
+s:drop()
+---
+...
diff --git a/test/engine/functional_idx.test.lua b/test/engine/functional_idx.test.lua
new file mode 100644
index 0000000..b4eb5b4
--- /dev/null
+++ b/test/engine/functional_idx.test.lua
@@ -0,0 +1,35 @@
+msgpack = require('msgpack')
+env = require('test_run')
+test_run = env.new()
+engine = test_run:get_cfg('engine')
+
+format = {{'id','unsigned'}, {'name','string'},{'address', 'string'}}
+s = box.schema.space.create('clients', {engine = engine, format=format})
+addr_extractor = [[return function(tuple) if type(tuple.address) ~= 'string' then return nil, 'Invalid field type' end local t = tuple.address:lower():split() for k,v in pairs(t) do t[k] = {v} end return t end]]
+addr_extractor_format = {{type='str', collation='unicode_ci'}}
+s:create_index('address', {func_code = addr_extractor, func_format = addr_extractor_format, unique=true})
+pk = s:create_index('pk')
+s:insert({1, 'Vasya Pupkin', 'Russia Moscow Dolgoprudny Moscow Street 9А'})
+s:create_index('address', {func_code = addr_extractor, unique=true})
+s:create_index('address', {func_format = addr_extractor_format, unique=true})
+s:create_index('address', {func_code = addr_extractor..'err', func_format = addr_extractor_format, unique=true})
+addr_low = s:create_index('address', {func_code = addr_extractor, func_format = addr_extractor_format, unique=true, is_multikey=true})
+assert(box.space.clients:on_replace()[1] == addr_low.functional.trigger)
+s:insert({2, 'James Bond', 'GB London Mi6'})
+s:insert({3, 'Kirill Alekseevich', 'Russia Moscow Dolgoprudny RocketBuilders 1'})
+s:insert({4, 'Jack London', 'GB'})
+addr_low:count()
+addr_low:count({'moscow'})
+addr_low:count({'gb'})
+addr_extractor = [[return function(tuple) if type(tuple.address) ~= 'string' then return nil, 'Invalid field type' end return tuple.address:upper():split()[1] end]]
+addr_high = s:create_index('address2', {func_code = addr_extractor, func_format = addr_extractor_format, unique=true, is_multikey=false})
+addr_high:select()
+addr_high:select({}, {limit = 1})
+addr_high:select({}, {limit = 1, offset=1})
+addr_high:select({'Moscow'})
+for _, v in addr_high:pairs() do print(v.name) end
+_ = addr_low:delete({'moscow'})
+addr_high:select({'MOSCOW'})
+addr_high:drop()
+
+s:drop()
--
2.7.4
^ permalink raw reply [flat|nested] 5+ messages in thread