[tarantool-patches] Re: [PATCH v4 4/4] box: introduce functional indexes

Kirill Shcherbatov kshcherbatov at tarantool.org
Thu Jul 25 14:20:06 MSK 2019


On 24.07.2019 23:22, Konstantin Osipov wrote:
> * Kirill Shcherbatov <kshcherbatov at tarantool.org> [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 <lualib.h>
 } /* 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





More information about the Tarantool-patches mailing list