From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Subject: Re: [tarantool-patches] Re: [PATCH v4 4/4] box: introduce functional indexes References: <20190724202242.GF7651@atlas> From: Kirill Shcherbatov Message-ID: <299c2b2f-33ee-b54c-0ff8-389d2f51d548@tarantool.org> Date: Thu, 25 Jul 2019 14:20:06 +0300 MIME-Version: 1.0 In-Reply-To: <20190724202242.GF7651@atlas> Content-Type: text/plain; charset=utf-8 Content-Language: en-US Content-Transfer-Encoding: 7bit To: tarantool-patches@freelists.org, Konstantin Osipov Cc: vdavydov.dev@gmail.com List-ID: On 24.07.2019 23:22, Konstantin Osipov wrote: > * Kirill Shcherbatov [19/07/24 10:38]: >> lua_code = [[function(tuple) >> local address = string.split(tuple[2]) >> local ret = {} >> for _, v in pairs(address) do >> table.insert(ret, {utf8.upper(v)}) >> end >> return unpack(ret) >> end]] > > I think the convention should be that a multikey function must always > return a table (a msgpack array for a C function). > > It's OK to wrap every key into a table as long as it simplifies > the code (key part count is encoded automatically by struct port), but otherwise > I would stick to scalars. Ok, now a user must implicitly specify is_multikey and return a table of keys; The corresponding change diff is below: ====================================================== --- src/box/index_def.h | 4 +++ src/box/key_def.h | 2 +- src/box/key_list.h | 2 +- src/box/index_def.c | 2 ++ src/box/key_def.c | 5 +-- src/box/key_list.c | 26 ++++++++++++-- src/box/lua/key_def.c | 2 +- src/box/memtx_engine.c | 2 ++ src/box/memtx_tree.c | 3 ++ src/box/sql.c | 3 +- src/box/sql/build.c | 2 +- src/box/sql/select.c | 3 +- src/box/sql/where.c | 2 +- src/box/vinyl.c | 4 ++- test/unit/luaT_tuple_new.c | 2 +- test/unit/merger.test.c | 6 ++-- src/box/alter.cc | 3 +- src/box/lua/space.cc | 18 +++++++++- src/box/schema.cc | 3 +- test/engine/functional.result | 62 ++++++++++++++++++++++++++------- test/engine/functional.test.lua | 38 +++++++++++++------- 21 files changed, 151 insertions(+), 43 deletions(-) diff --git a/src/box/index_def.h b/src/box/index_def.h index 71b6db32d..f7788c63e 100644 --- a/src/box/index_def.h +++ b/src/box/index_def.h @@ -165,6 +165,8 @@ struct index_opts { struct index_stat *stat; /** Identifier of the functional index function. */ uint32_t func_id; + /** Whether functional index extractor is multikey. */ + bool is_multikey; }; extern const struct index_opts index_opts_default; @@ -211,6 +213,8 @@ index_opts_cmp(const struct index_opts *o1, const struct index_opts *o2) return o1->bloom_fpr < o2->bloom_fpr ? -1 : 1; if (o1->func_id != o2->func_id) return o1->func_id - o2->func_id; + if (o1->is_multikey != o2->is_multikey) + return o1->is_multikey - o2->is_multikey; return 0; } diff --git a/src/box/key_def.h b/src/box/key_def.h index 5c898fc87..4357408b7 100644 --- a/src/box/key_def.h +++ b/src/box/key_def.h @@ -340,7 +340,7 @@ key_def_sizeof(uint32_t part_count, uint32_t path_pool_size) */ struct key_def * key_def_new(const struct key_part_def *parts, uint32_t part_count, - bool is_functional); + bool is_functional, bool is_multikey); /** * Dump part definitions of the given key def. diff --git a/src/box/key_list.h b/src/box/key_list.h index 9107dfeeb..f7ed344c8 100644 --- a/src/box/key_list.h +++ b/src/box/key_list.h @@ -52,7 +52,7 @@ struct tuple; * Uses fiber region to allocate memory. */ const char * -key_list_create(struct tuple *tuple, struct func *func, +key_list_create(struct tuple *tuple, struct func *func, bool is_multikey, const char **data_end, uint32_t *key_count); /** diff --git a/src/box/index_def.c b/src/box/index_def.c index 7d06c65f3..420ca14f4 100644 --- a/src/box/index_def.c +++ b/src/box/index_def.c @@ -51,6 +51,7 @@ const struct index_opts index_opts_default = { /* .lsn = */ 0, /* .stat = */ NULL, /* .func = */ 0, + /* .is_multikey = */ false, }; const struct opt_def index_opts_reg[] = { @@ -65,6 +66,7 @@ const struct opt_def index_opts_reg[] = { OPT_DEF("bloom_fpr", OPT_FLOAT, struct index_opts, bloom_fpr), OPT_DEF("lsn", OPT_INT64, struct index_opts, lsn), OPT_DEF("func", OPT_UINT32, struct index_opts, func_id), + OPT_DEF("is_multikey", OPT_BOOL, struct index_opts, is_multikey), OPT_DEF_LEGACY("sql"), OPT_END, }; diff --git a/src/box/key_def.c b/src/box/key_def.c index ffc34db59..ea46a1583 100644 --- a/src/box/key_def.c +++ b/src/box/key_def.c @@ -242,7 +242,7 @@ key_def_set_part(struct key_def *def, uint32_t part_no, uint32_t fieldno, struct key_def * key_def_new(const struct key_part_def *parts, uint32_t part_count, - bool is_functional) + bool is_functional, bool is_multikey) { size_t sz = 0; for (uint32_t i = 0; i < part_count; i++) @@ -279,6 +279,7 @@ key_def_new(const struct key_part_def *parts, uint32_t part_count, goto error; } if (is_functional) { + def->is_multikey = is_multikey; def->part_count_for_func_index = part_count; if (!key_def_is_sequential(def) || parts->fieldno != 0 || def->has_json_paths) { @@ -842,7 +843,7 @@ key_def_find_pk_in_cmp_def(const struct key_def *cmp_def, } /* Finally, allocate the new key definition. */ - extracted_def = key_def_new(parts, pk_def->part_count, false); + extracted_def = key_def_new(parts, pk_def->part_count, false, false); out: region_truncate(region, region_svp); return extracted_def; diff --git a/src/box/key_list.c b/src/box/key_list.c index 5b5552116..6b800d9c7 100644 --- a/src/box/key_list.c +++ b/src/box/key_list.c @@ -33,6 +33,7 @@ #include "errcode.h" #include "diag.h" #include "func.h" +#include "fiber.h" #include "key_def.h" #include "port.h" #include "tt_static.h" @@ -40,9 +41,12 @@ #include "tuple_compare.h" const char * -key_list_create(struct tuple *tuple, struct func *func, +key_list_create(struct tuple *tuple, struct func *func, bool is_multikey, const char **data_end, uint32_t *key_count) { + struct region *region = &fiber()->gc; + size_t region_svp = region_used(region); + struct port out_port, in_port; port_tuple_create(&in_port); port_tuple_add(&in_port, tuple); @@ -52,15 +56,31 @@ key_list_create(struct tuple *tuple, struct func *func, goto error; uint32_t key_data_sz; const char *key_data = port_get_msgpack(&out_port, &key_data_sz); + *data_end = key_data + key_data_sz; port_destroy(&out_port); if (key_data == NULL) goto error; assert(mp_typeof(*key_data) == MP_ARRAY); - *data_end = key_data + key_data_sz; - *key_count = mp_decode_array(&key_data); + if (mp_decode_array(&key_data) != 1) { + diag_set(ClientError, ER_FUNC_INDEX_FUNC, + func->def->name, "to many values were returned"); + goto error; + } + if (is_multikey) { + if (mp_typeof(*key_data) != MP_ARRAY) { + diag_set(ClientError, ER_FUNC_INDEX_FUNC, + func->def->name, + "multikey function mustn't return scalar"); + goto error; + } + *key_count = mp_decode_array(&key_data); + } else { + *key_count = 1; + } return key_data; error: + region_truncate(region, region_svp); diag_set(ClientError, ER_FUNC_INDEX_FUNC, func->def->name, diag_last_error(diag_get())->errmsg); return NULL; diff --git a/src/box/lua/key_def.c b/src/box/lua/key_def.c index 3a3a5ec0c..e572fec1b 100644 --- a/src/box/lua/key_def.c +++ b/src/box/lua/key_def.c @@ -445,7 +445,7 @@ lbox_key_def_new(struct lua_State *L) lua_pop(L, 1); } - struct key_def *key_def = key_def_new(parts, part_count, false); + struct key_def *key_def = key_def_new(parts, part_count, false, false); region_truncate(region, region_svp); if (key_def == NULL) return luaT_error(L); diff --git a/src/box/memtx_engine.c b/src/box/memtx_engine.c index 9f8d5a6a4..8589d2f1e 100644 --- a/src/box/memtx_engine.c +++ b/src/box/memtx_engine.c @@ -1250,6 +1250,8 @@ memtx_index_def_change_requires_rebuild(struct index *index, return true; if (old_def->opts.func_id != new_def->opts.func_id) return true; + if (old_def->opts.is_multikey != new_def->opts.is_multikey) + return true; const struct key_def *old_cmp_def, *new_cmp_def; if (index_depends_on_pk(index)) { diff --git a/src/box/memtx_tree.c b/src/box/memtx_tree.c index f24fec15f..b11f09dd5 100644 --- a/src/box/memtx_tree.c +++ b/src/box/memtx_tree.c @@ -863,6 +863,7 @@ memtx_tree_index_replace_functional(struct index *base, struct tuple *old_tuple, struct key_list_iterator it; if (new_tuple != NULL) { key_list = key_list_create(new_tuple, cmp_def->func_index_func, + cmp_def->is_multikey, &key_list_end, &key_cnt); if (key_list == NULL) goto end; @@ -926,6 +927,7 @@ memtx_tree_index_replace_functional(struct index *base, struct tuple *old_tuple, } if (old_tuple != NULL) { key_list = key_list_create(old_tuple, cmp_def->func_index_func, + cmp_def->is_multikey, &key_list_end, &key_cnt); if (key_list == NULL) goto end; @@ -1101,6 +1103,7 @@ memtx_tree_index_build_next_functional(struct index *base, struct tuple *tuple) const char *key_list; const char *key_list_end; key_list = key_list_create(tuple, cmp_def->func_index_func, + cmp_def->is_multikey, &key_list_end, &key_cnt); if (key_list == NULL) return -1; diff --git a/src/box/sql.c b/src/box/sql.c index 0ab3a506f..ba5bcbf10 100644 --- a/src/box/sql.c +++ b/src/box/sql.c @@ -347,7 +347,8 @@ sql_ephemeral_space_create(uint32_t field_count, struct sql_key_info *key_info) } } struct key_def *ephemer_key_def = key_def_new(ephemer_key_parts, - field_count, false); + field_count, false, + false); if (ephemer_key_def == NULL) return NULL; diff --git a/src/box/sql/build.c b/src/box/sql/build.c index 0a6759e41..b965c7c5c 100644 --- a/src/box/sql/build.c +++ b/src/box/sql/build.c @@ -2338,7 +2338,7 @@ index_fill_def(struct Parse *parse, struct index *index, part->coll_id = coll_id; part->path = NULL; } - key_def = key_def_new(key_parts, expr_list->nExpr, false); + key_def = key_def_new(key_parts, expr_list->nExpr, false, false); if (key_def == NULL) goto tnt_error; /* diff --git a/src/box/sql/select.c b/src/box/sql/select.c index c312f61f1..bb7fa783e 100644 --- a/src/box/sql/select.c +++ b/src/box/sql/select.c @@ -1438,7 +1438,8 @@ sql_key_info_to_key_def(struct sql_key_info *key_info) { if (key_info->key_def == NULL) { key_info->key_def = key_def_new(key_info->parts, - key_info->part_count, false); + key_info->part_count, + false, false); } return key_info->key_def; } diff --git a/src/box/sql/where.c b/src/box/sql/where.c index ed507bf4d..b85243a88 100644 --- a/src/box/sql/where.c +++ b/src/box/sql/where.c @@ -2775,7 +2775,7 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder, /* WHERE clause information */ part.coll_id = COLL_NONE; part.path = NULL; - struct key_def *key_def = key_def_new(&part, 1, false); + struct key_def *key_def = key_def_new(&part, 1, false, false); if (key_def == NULL) { tnt_error: pWInfo->pParse->is_aborted = true; diff --git a/src/box/vinyl.c b/src/box/vinyl.c index c938350d9..dce32b574 100644 --- a/src/box/vinyl.c +++ b/src/box/vinyl.c @@ -993,6 +993,8 @@ vinyl_index_def_change_requires_rebuild(struct index *index, return true; if (old_def->opts.func_id != new_def->opts.func_id) return true; + if (old_def->opts.is_multikey != new_def->opts.is_multikey) + return true; assert(index_depends_on_pk(index)); const struct key_def *old_cmp_def = old_def->cmp_def; @@ -3172,7 +3174,7 @@ vy_send_lsm(struct vy_join_ctx *ctx, struct vy_lsm_recovery_info *lsm_info) /* Create key definition and tuple format. */ ctx->key_def = key_def_new(lsm_info->key_parts, - lsm_info->key_part_count, false); + lsm_info->key_part_count, false, false); if (ctx->key_def == NULL) goto out; ctx->format = vy_stmt_format_new(&ctx->env->stmt_env, &ctx->key_def, 1, diff --git a/test/unit/luaT_tuple_new.c b/test/unit/luaT_tuple_new.c index 609d64e45..d5c59c975 100644 --- a/test/unit/luaT_tuple_new.c +++ b/test/unit/luaT_tuple_new.c @@ -124,7 +124,7 @@ test_basic(struct lua_State *L) part.nullable_action = ON_CONFLICT_ACTION_DEFAULT; part.sort_order = SORT_ORDER_ASC; part.path = NULL; - struct key_def *key_def = key_def_new(&part, 1, false); + struct key_def *key_def = key_def_new(&part, 1, false, false); box_tuple_format_t *another_format = box_tuple_format_new(&key_def, 1); key_def_delete(key_def); diff --git a/test/unit/merger.test.c b/test/unit/merger.test.c index 345a2364e..8a4379a10 100644 --- a/test/unit/merger.test.c +++ b/test/unit/merger.test.c @@ -214,7 +214,8 @@ test_merger(struct tuple_format *format) merge_source_array_new(true), }; - struct key_def *key_def = key_def_new(&key_part_unsigned, 1, false); + struct key_def *key_def = key_def_new(&key_part_unsigned, 1, + false, false); struct merge_source *merger = merger_new(key_def, sources, source_count, false); key_def_delete(key_def); @@ -252,7 +253,8 @@ test_basic() plan(4); header(); - struct key_def *key_def = key_def_new(&key_part_integer, 1, false); + struct key_def *key_def = key_def_new(&key_part_integer, 1, + false, false); struct tuple_format *format = box_tuple_format_new(&key_def, 1); assert(format != NULL); diff --git a/src/box/alter.cc b/src/box/alter.cc index 3363e2b85..0b23dd6bb 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -302,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); + key_def = key_def_new(part_def, part_count, opts.func_id > 0, + opts.is_multikey); if (key_def == NULL) diag_raise(); struct index_def *index_def = diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc index 72c6bcada..6a778b2f2 100644 --- a/src/box/lua/space.cc +++ b/src/box/lua/space.cc @@ -42,6 +42,8 @@ extern "C" { #include } /* extern "C" */ +#include "box/func.h" +#include "box/func_def.h" #include "box/space.h" #include "box/schema.h" #include "box/user_def.h" @@ -336,8 +338,22 @@ lbox_fillspace(struct lua_State *L, struct space *space, int i) } if (index_opts->func_id > 0) { + lua_pushstring(L, "func"); + lua_newtable(L); + lua_pushnumber(L, index_opts->func_id); - lua_setfield(L, -2, "func_id"); + lua_setfield(L, -2, "fid"); + + lua_pushboolean(L, index_opts->is_multikey); + lua_setfield(L, -2, "is_multikey"); + + struct func *func = func_by_id(index_opts->func_id); + if (func != NULL) { + lua_pushstring(L, func->def->name); + lua_setfield(L, -2, "name"); + } + + lua_settable(L, -3); } lua_pushstring(L, index_type_strs[index_def->type]); diff --git a/src/box/schema.cc b/src/box/schema.cc index 5d4a3ff00..d5ccdd1b6 100644 --- a/src/box/schema.cc +++ b/src/box/schema.cc @@ -266,7 +266,8 @@ sc_space_new(uint32_t id, const char *name, uint32_t key_part_count, struct trigger *replace_trigger) { - struct key_def *key_def = key_def_new(key_parts, key_part_count, false); + struct key_def *key_def = key_def_new(key_parts, key_part_count, + false, false); if (key_def == NULL) diag_raise(); auto key_def_guard = diff --git a/test/engine/functional.result b/test/engine/functional.result index 8a0eba614..ca8d0e366 100644 --- a/test/engine/functional.result +++ b/test/engine/functional.result @@ -159,14 +159,33 @@ idx:drop() | --- | ... --- Invalid functional index extractor routine return: the second returned key invalid. +-- Invalid functional index extractor routine return: invalid return format for multikey index. lua_code = [[function(tuple) return {"hello", "world"}, {1, 2} end]] | --- | ... +box.schema.func.create('invalidreturn2', {body = lua_code, is_deterministic = true, is_sandboxed = true}) + | --- + | ... +idx = s:create_index('idx', {func = box.func.invalidreturn2.id, is_multikey = true, parts = {{1, 'unsigned'}, {2, 'unsigned'}}}) + | --- + | ... +s:insert({1}) + | --- + | - error: 'Failed to build a key for functional index ''invalidreturn2'': Failed to + | build a key for functional index ''invalidreturn2'': to many values were returned' + | ... +idx:drop() + | --- + | ... + +-- Invalid functional index extractor routine return: the second returned key invalid. +lua_code = [[function(tuple) return {{"hello", "world"}, {1, 2}} end]] + | --- + | ... box.schema.func.create('invalidreturn3', {body = lua_code, is_deterministic = true, is_sandboxed = true}) | --- | ... -idx = s:create_index('idx', {func = box.func.invalidreturn3.id, parts = {{1, 'unsigned'}, {2, 'unsigned'}}}) +idx = s:create_index('idx', {func = box.func.invalidreturn3.id, is_multikey = true, parts = {{1, 'unsigned'}, {2, 'unsigned'}}}) | --- | ... s:insert({1}) @@ -178,6 +197,25 @@ idx:drop() | --- | ... +-- Invalid functional index extractor routine return: multikey return in case of regular index. +lua_code = [[function(tuple) return {{"hello", "world"}, {1, 2}} end]] + | --- + | ... +box.schema.func.create('invalidreturn4', {body = lua_code, is_deterministic = true, is_sandboxed = true}) + | --- + | ... +idx = s:create_index('idx', {func = box.func.invalidreturn4.id, parts = {{1, 'unsigned'}, {2, 'unsigned'}}}) + | --- + | ... +s:insert({1}) + | --- + | - error: 'Failed to build a key for functional index ''invalidreturn4'': Supplied + | key type of part 0 does not match index part type: expected unsigned' + | ... +idx:drop() + | --- + | ... + -- Invalid functional extractor: runtime extractor error test_run:cmd("setopt delimiter ';'") | --- @@ -273,7 +311,7 @@ collectgarbage() s = box.schema.space.create('withdata', {engine = engine}) | --- | ... -lua_code = [[function(tuple) return {tuple[1] + tuple[2]}, {tuple[1] + tuple[2]}, {tuple[1]} end]] +lua_code = [[function(tuple) return {{tuple[1] + tuple[2]}, {tuple[1] + tuple[2]}, {tuple[1]}} end]] | --- | ... box.schema.func.create('extr', {body = lua_code, is_deterministic = true, is_sandboxed = true}) @@ -282,7 +320,7 @@ box.schema.func.create('extr', {body = lua_code, is_deterministic = true, is_san pk = s:create_index('pk') | --- | ... -idx = s:create_index('idx', {unique = true, func = box.func.extr.id, parts = {{1, 'integer'}}}) +idx = s:create_index('idx', {unique = true, func = box.func.extr.id, is_multikey = true, parts = {{1, 'integer'}}}) | --- | ... s:insert({1, 2}) @@ -335,7 +373,7 @@ box.schema.func.drop('extr') s = box.schema.space.create('withdata', {engine = engine}) | --- | ... -lua_code = [[function(tuple) return {600 + tuple[1], 600 + tuple[2]}, {500 + tuple[1], 500 + tuple[2]} end]] +lua_code = [[function(tuple) return {{600 + tuple[1], 600 + tuple[2]}, {500 + tuple[1], 500 + tuple[2]}} end]] | --- | ... box.schema.func.create('extr', {body = lua_code, is_deterministic = true, is_sandboxed = true}) @@ -344,7 +382,7 @@ box.schema.func.create('extr', {body = lua_code, is_deterministic = true, is_san pk = s:create_index('pk') | --- | ... -idx = s:create_index('idx', {unique = true, func = box.func.extr.id, parts = {{1, 'integer'}, {2, 'integer'}}}) +idx = s:create_index('idx', {unique = true, func = box.func.extr.id, is_multikey = true, parts = {{1, 'integer'}, {2, 'integer'}}}) | --- | ... s:insert({1, 2}) @@ -390,7 +428,7 @@ box.schema.func.drop('extr') s = box.schema.space.create('withdata', {engine = engine}) | --- | ... -lua_code = [[function(tuple) return {500 + tuple[1]}, {500 + tuple[2]}, {500 + tuple[2]} end]] +lua_code = [[function(tuple) return {{500 + tuple[1]}, {500 + tuple[2]}, {500 + tuple[2]}} end]] | --- | ... box.schema.func.create('extr', {body = lua_code, is_deterministic = true, is_sandboxed = true}) @@ -399,7 +437,7 @@ box.schema.func.create('extr', {body = lua_code, is_deterministic = true, is_san pk = s:create_index('pk') | --- | ... -idx = s:create_index('idx', {unique = false, func = box.func.extr.id, parts = {{1, 'integer'}}}) +idx = s:create_index('idx', {unique = false, func = box.func.extr.id, is_multikey = true, parts = {{1, 'integer'}}}) | --- | ... s:insert({1, 2}) @@ -521,7 +559,7 @@ lua_code = [[function(tuple) local address = string.split(tuple[2]) local ret = {} for _, v in pairs(address) do table.insert(ret, {utf8.upper(v)}) end - return unpack(ret) + return ret end]] test_run:cmd("setopt delimiter ''"); | --- @@ -529,7 +567,7 @@ test_run:cmd("setopt delimiter ''"); box.schema.func.create('addr_extractor', {body = lua_code, is_deterministic = true, is_sandboxed = true}) | --- | ... -idx = s:create_index('addr', {unique = false, func = box.func.addr_extractor.id, parts = {{1, 'string', collation = 'unicode_ci'}}}) +idx = s:create_index('addr', {unique = false, func = box.func.addr_extractor.id, is_multikey = true, parts = {{1, 'string', collation = 'unicode_ci'}}}) | --- | ... idx:select('uk') @@ -559,13 +597,13 @@ s = box.schema.space.create('withdata', {engine = engine}) pk = s:create_index('pk') | --- | ... -lua_code = [[function(tuple) if tuple[1] % 2 == 1 then return {tuple[1]} end end]] +lua_code = [[function(tuple) if tuple[1] % 2 == 1 then return {{tuple[1]}} else return {} end end]] | --- | ... box.schema.func.create('extr', {body = lua_code, is_deterministic = true, is_sandboxed = true}) | --- | ... -idx = s:create_index('idx', {unique = true, func = box.func.extr.id, parts = {{1, 'integer'}}}) +idx = s:create_index('idx', {unique = true, func = box.func.extr.id, is_multikey = true, parts = {{1, 'integer'}}}) | --- | ... s:insert({1}) diff --git a/test/engine/functional.test.lua b/test/engine/functional.test.lua index 92a74aab7..ae88f2900 100644 --- a/test/engine/functional.test.lua +++ b/test/engine/functional.test.lua @@ -60,10 +60,24 @@ idx = s:create_index('idx', {func = box.func.invalidreturn1.id, parts = {{1, 'un s:insert({1}) idx:drop() --- Invalid functional index extractor routine return: the second returned key invalid. +-- Invalid functional index extractor routine return: invalid return format for multikey index. lua_code = [[function(tuple) return {"hello", "world"}, {1, 2} end]] +box.schema.func.create('invalidreturn2', {body = lua_code, is_deterministic = true, is_sandboxed = true}) +idx = s:create_index('idx', {func = box.func.invalidreturn2.id, is_multikey = true, parts = {{1, 'unsigned'}, {2, 'unsigned'}}}) +s:insert({1}) +idx:drop() + +-- Invalid functional index extractor routine return: the second returned key invalid. +lua_code = [[function(tuple) return {{"hello", "world"}, {1, 2}} end]] box.schema.func.create('invalidreturn3', {body = lua_code, is_deterministic = true, is_sandboxed = true}) -idx = s:create_index('idx', {func = box.func.invalidreturn3.id, parts = {{1, 'unsigned'}, {2, 'unsigned'}}}) +idx = s:create_index('idx', {func = box.func.invalidreturn3.id, is_multikey = true, parts = {{1, 'unsigned'}, {2, 'unsigned'}}}) +s:insert({1}) +idx:drop() + +-- Invalid functional index extractor routine return: multikey return in case of regular index. +lua_code = [[function(tuple) return {{"hello", "world"}, {1, 2}} end]] +box.schema.func.create('invalidreturn4', {body = lua_code, is_deterministic = true, is_sandboxed = true}) +idx = s:create_index('idx', {func = box.func.invalidreturn4.id, parts = {{1, 'unsigned'}, {2, 'unsigned'}}}) s:insert({1}) idx:drop() @@ -102,10 +116,10 @@ collectgarbage() -- Multikey functional index. s = box.schema.space.create('withdata', {engine = engine}) -lua_code = [[function(tuple) return {tuple[1] + tuple[2]}, {tuple[1] + tuple[2]}, {tuple[1]} end]] +lua_code = [[function(tuple) return {{tuple[1] + tuple[2]}, {tuple[1] + tuple[2]}, {tuple[1]}} end]] box.schema.func.create('extr', {body = lua_code, is_deterministic = true, is_sandboxed = true}) pk = s:create_index('pk') -idx = s:create_index('idx', {unique = true, func = box.func.extr.id, parts = {{1, 'integer'}}}) +idx = s:create_index('idx', {unique = true, func = box.func.extr.id, is_multikey = true, parts = {{1, 'integer'}}}) s:insert({1, 2}) s:insert({3, 5}) s:insert({5, 3}) @@ -120,10 +134,10 @@ box.schema.func.drop('extr') -- Multikey multipart functional index. s = box.schema.space.create('withdata', {engine = engine}) -lua_code = [[function(tuple) return {600 + tuple[1], 600 + tuple[2]}, {500 + tuple[1], 500 + tuple[2]} end]] +lua_code = [[function(tuple) return {{600 + tuple[1], 600 + tuple[2]}, {500 + tuple[1], 500 + tuple[2]}} end]] box.schema.func.create('extr', {body = lua_code, is_deterministic = true, is_sandboxed = true}) pk = s:create_index('pk') -idx = s:create_index('idx', {unique = true, func = box.func.extr.id, parts = {{1, 'integer'}, {2, 'integer'}}}) +idx = s:create_index('idx', {unique = true, func = box.func.extr.id, is_multikey = true, parts = {{1, 'integer'}, {2, 'integer'}}}) s:insert({1, 2}) s:insert({2, 1}) s:insert({3, 3}) @@ -136,10 +150,10 @@ box.schema.func.drop('extr') -- Multikey non-unique functional index. s = box.schema.space.create('withdata', {engine = engine}) -lua_code = [[function(tuple) return {500 + tuple[1]}, {500 + tuple[2]}, {500 + tuple[2]} end]] +lua_code = [[function(tuple) return {{500 + tuple[1]}, {500 + tuple[2]}, {500 + tuple[2]}} end]] box.schema.func.create('extr', {body = lua_code, is_deterministic = true, is_sandboxed = true}) pk = s:create_index('pk') -idx = s:create_index('idx', {unique = false, func = box.func.extr.id, parts = {{1, 'integer'}}}) +idx = s:create_index('idx', {unique = false, func = box.func.extr.id, is_multikey = true, parts = {{1, 'integer'}}}) s:insert({1, 2}) s:insert({2, 1}) idx:select({501}) @@ -176,11 +190,11 @@ lua_code = [[function(tuple) local address = string.split(tuple[2]) local ret = {} for _, v in pairs(address) do table.insert(ret, {utf8.upper(v)}) end - return unpack(ret) + return ret end]] test_run:cmd("setopt delimiter ''"); box.schema.func.create('addr_extractor', {body = lua_code, is_deterministic = true, is_sandboxed = true}) -idx = s:create_index('addr', {unique = false, func = box.func.addr_extractor.id, parts = {{1, 'string', collation = 'unicode_ci'}}}) +idx = s:create_index('addr', {unique = false, func = box.func.addr_extractor.id, is_multikey = true, parts = {{1, 'string', collation = 'unicode_ci'}}}) idx:select('uk') idx:select('Sis') s:drop() @@ -190,9 +204,9 @@ box.schema.func.drop('addr_extractor') -- Partial index with functional index extractor s = box.schema.space.create('withdata', {engine = engine}) pk = s:create_index('pk') -lua_code = [[function(tuple) if tuple[1] % 2 == 1 then return {tuple[1]} end end]] +lua_code = [[function(tuple) if tuple[1] % 2 == 1 then return {{tuple[1]}} else return {} end end]] box.schema.func.create('extr', {body = lua_code, is_deterministic = true, is_sandboxed = true}) -idx = s:create_index('idx', {unique = true, func = box.func.extr.id, parts = {{1, 'integer'}}}) +idx = s:create_index('idx', {unique = true, func = box.func.extr.id, is_multikey = true, parts = {{1, 'integer'}}}) s:insert({1}) s:insert({2}) s:insert({3}) -- 2.22.0