diff --git a/changelogs/unreleased/gh-7356-populate-index-with-keydef.md b/changelogs/unreleased/gh-7356-populate-index-with-keydef.md new file mode 100644 index 0000000000000000000000000000000000000000..8c11872699cfe44ef5da9d47af1e5943511c9446 --- /dev/null +++ b/changelogs/unreleased/gh-7356-populate-index-with-keydef.md @@ -0,0 +1,5 @@ +## feature/core + +* Now `index_object.parts` contains the following methods, similar to the + `key_def` Lua module: `extract_key()`, `compare()`, `compare_with_key()`, + `merge()` (gh-7356). diff --git a/src/box/lua/key_def.c b/src/box/lua/key_def.c index e5fc370e49803ae6167fdd78bb2c9009b0c653de..b520251ea1ea0d2ee1936a76c75f8d2b9f2b5d98 100644 --- a/src/box/lua/key_def.c +++ b/src/box/lua/key_def.c @@ -40,8 +40,39 @@ static uint32_t CTID_STRUCT_KEY_DEF_REF = 0; +/** + * Free a key_def from a Lua code. + */ +static int +lbox_key_def_gc(struct lua_State *L) +{ + struct key_def *key_def = luaT_is_key_def(L, 1); + assert(key_def != NULL); + key_def_delete(key_def); + return 0; +} + +/** + * Push key_def as a cdata object to a Lua stack. This function takes ownership + * of key_def, and sets finalizer lbox_key_def_gc for it. + */ +static void +luaT_push_key_def_nodup(struct lua_State *L, const struct key_def *key_def) +{ + void *ptr = luaL_pushcdata(L, CTID_STRUCT_KEY_DEF_REF); + *(const struct key_def **)ptr = key_def; + lua_pushcfunction(L, lbox_key_def_gc); + luaL_setcdatagc(L, -2); +} + void luaT_push_key_def(struct lua_State *L, const struct key_def *key_def) +{ + luaT_push_key_def_nodup(L, key_def_dup(key_def)); +} + +void +luaT_push_key_def_parts(struct lua_State *L, const struct key_def *key_def) { lua_createtable(L, key_def->part_count, 0); for (uint32_t i = 0; i < key_def->part_count; ++i) { @@ -242,7 +273,7 @@ luaT_key_def_check_tuple(struct lua_State *L, struct key_def *key_def, int idx) } struct key_def * -luaT_check_key_def(struct lua_State *L, int idx) +luaT_is_key_def(struct lua_State *L, int idx) { if (lua_type(L, idx) != LUA_TCDATA) return NULL; @@ -254,33 +285,16 @@ luaT_check_key_def(struct lua_State *L, int idx) return *key_def_ptr; } -/** - * Free a key_def from a Lua code. - */ -static int -lbox_key_def_gc(struct lua_State *L) +int +luaT_key_def_extract_key(struct lua_State *L, int idx) { - struct key_def *key_def = luaT_check_key_def(L, 1); + struct key_def *key_def = luaT_is_key_def(L, idx); assert(key_def != NULL); - key_def_delete(key_def); - return 0; -} -/** - * Extract key from tuple by given key definition and return - * tuple representing this key. - * Push the new key tuple as cdata to a LUA stack on success. - * Raise error otherwise. - */ -static int -lbox_key_def_extract_key(struct lua_State *L) -{ - struct key_def *key_def; - if (lua_gettop(L) != 2 || (key_def = luaT_check_key_def(L, 1)) == NULL) - return luaL_error(L, "Usage: key_def:extract_key(tuple)"); - - struct tuple *tuple; - if ((tuple = luaT_key_def_check_tuple(L, key_def, 2)) == NULL) + if (key_def->is_multikey) + return luaL_error(L, "multikey path is unsupported"); + struct tuple *tuple = luaT_key_def_check_tuple(L, key_def, -1); + if (tuple == NULL) return luaT_error(L); struct region *region = &fiber()->gc; @@ -300,24 +314,14 @@ lbox_key_def_extract_key(struct lua_State *L) return 1; } -/** - * Compare tuples using the key definition. - * Push 0 if key_fields(tuple_a) == key_fields(tuple_b) - * <0 if key_fields(tuple_a) < key_fields(tuple_b) - * >0 if key_fields(tuple_a) > key_fields(tuple_b) - * integer to a LUA stack on success. - * Raise error otherwise. - */ -static int -lbox_key_def_compare(struct lua_State *L) +int +luaT_key_def_compare(struct lua_State *L, int idx) { - struct key_def *key_def; - if (lua_gettop(L) != 3 || - (key_def = luaT_check_key_def(L, 1)) == NULL) { - return luaL_error(L, "Usage: key_def:" - "compare(tuple_a, tuple_b)"); - } + struct key_def *key_def = luaT_is_key_def(L, idx); + assert(key_def != NULL); + if (key_def->is_multikey) + return luaL_error(L, "multikey path is unsupported"); if (key_def->tuple_compare == NULL) { enum field_type type = key_def_incomparable_type(key_def); assert(type != field_type_MAX); @@ -326,10 +330,11 @@ lbox_key_def_compare(struct lua_State *L) return luaT_error(L); } - struct tuple *tuple_a, *tuple_b; - if ((tuple_a = luaT_key_def_check_tuple(L, key_def, 2)) == NULL) + struct tuple *tuple_a = luaT_key_def_check_tuple(L, key_def, -2); + if (tuple_a == NULL) return luaT_error(L); - if ((tuple_b = luaT_key_def_check_tuple(L, key_def, 3)) == NULL) { + struct tuple *tuple_b = luaT_key_def_check_tuple(L, key_def, -1); + if (tuple_b == NULL) { tuple_unref(tuple_a); return luaT_error(L); } @@ -341,24 +346,14 @@ lbox_key_def_compare(struct lua_State *L) return 1; } -/** - * Compare tuple with key using the key definition. - * Push 0 if key_fields(tuple) == parts(key) - * <0 if key_fields(tuple) < parts(key) - * >0 if key_fields(tuple) > parts(key) - * integer to a LUA stack on success. - * Raise error otherwise. - */ -static int -lbox_key_def_compare_with_key(struct lua_State *L) +int +luaT_key_def_compare_with_key(struct lua_State *L, int idx) { - struct key_def *key_def; - if (lua_gettop(L) != 3 || - (key_def = luaT_check_key_def(L, 1)) == NULL) { - return luaL_error(L, "Usage: key_def:" - "compare_with_key(tuple, key)"); - } + struct key_def *key_def = luaT_is_key_def(L, idx); + assert(key_def != NULL); + if (key_def->is_multikey) + return luaL_error(L, "multikey path is unsupported"); if (key_def->tuple_compare_with_key == NULL) { enum field_type type = key_def_incomparable_type(key_def); assert(type != field_type_MAX); @@ -367,13 +362,13 @@ lbox_key_def_compare_with_key(struct lua_State *L) return luaT_error(L); } - struct tuple *tuple = luaT_key_def_check_tuple(L, key_def, 2); + struct tuple *tuple = luaT_key_def_check_tuple(L, key_def, -2); if (tuple == NULL) return luaT_error(L); struct region *region = &fiber()->gc; size_t region_svp = region_used(region); - const char *key = luaT_tuple_encode(L, 3, NULL); + const char *key = luaT_tuple_encode(L, -1, NULL); if (key == NULL || box_key_def_validate_key(key_def, key, NULL) != 0) { region_truncate(region, region_svp); tuple_unref(tuple); @@ -387,60 +382,94 @@ lbox_key_def_compare_with_key(struct lua_State *L) return 1; } -/** - * Construct and export to LUA a new key definition with a set - * union of key parts from first and second key defs. Parts of - * the new key_def consist of the first key_def's parts and those - * parts of the second key_def that were not among the first - * parts. - * Push the new key_def as cdata to a LUA stack on success. - * Raise error otherwise. - */ -static int -lbox_key_def_merge(struct lua_State *L) +int +luaT_key_def_merge(struct lua_State *L, int idx_a, int idx_b) { - struct key_def *key_def_a, *key_def_b; - if (lua_gettop(L) != 2 || - (key_def_a = luaT_check_key_def(L, 1)) == NULL || - (key_def_b = luaT_check_key_def(L, 2)) == NULL) - return luaL_error(L, "Usage: key_def:merge(second_key_def)"); - + struct key_def *key_def_a = luaT_is_key_def(L, idx_a); + struct key_def *key_def_b = luaT_is_key_def(L, idx_b); + assert(key_def_a != NULL); + assert(key_def_b != NULL); + + if (key_def_a->is_multikey) + return luaL_error(L, "multikey path is unsupported"); + assert(!key_def_b->is_multikey); struct key_def *new_key_def = key_def_merge(key_def_a, key_def_b); if (new_key_def == NULL) return luaT_error(L); - *(struct key_def **) luaL_pushcdata(L, - CTID_STRUCT_KEY_DEF_REF) = new_key_def; - lua_pushcfunction(L, lbox_key_def_gc); - luaL_setcdatagc(L, -2); + luaT_push_key_def(L, new_key_def); return 1; } +/** + * key_def:extract_key(tuple) + * Stack: [1] key_def; [2] tuple. + */ +static int +lbox_key_def_extract_key(struct lua_State *L) +{ + if (lua_gettop(L) != 2 || luaT_is_key_def(L, 1) == NULL) + return luaL_error(L, "Usage: key_def:extract_key(tuple)"); + return luaT_key_def_extract_key(L, 1); +} /** - * Push a new table representing a key_def to a Lua stack. + * key_def:compare(tuple_a, tuple_b) + * Stack: [1] key_def; [2] tuple_a; [3] tuple_b. */ static int -lbox_key_def_to_table(struct lua_State *L) +lbox_key_def_compare(struct lua_State *L) { - struct key_def *key_def; - if (lua_gettop(L) != 1 || (key_def = luaT_check_key_def(L, 1)) == NULL) - return luaL_error(L, "Usage: key_def:totable()"); + if (lua_gettop(L) != 3 || luaT_is_key_def(L, 1) == NULL) { + return luaL_error(L, "Usage: key_def:compare(" + "tuple_a, tuple_b)"); + } + return luaT_key_def_compare(L, 1); +} - luaT_push_key_def(L, key_def); - return 1; +/** + * key_def:compare_with_key(tuple, key) + * Stack: [1] key_def; [2] tuple; [3] key. + */ +static int +lbox_key_def_compare_with_key(struct lua_State *L) +{ + if (lua_gettop(L) != 3 || luaT_is_key_def(L, 1) == NULL) { + return luaL_error(L, "Usage: key_def:compare_with_key(" + "tuple, key)"); + } + return luaT_key_def_compare_with_key(L, 1); } /** - * Create a new key_def from a Lua table. - * - * Expected a table of key parts on the Lua stack. The format is - * the same as box.space.<...>.index.<...>.parts or corresponding - * net.box's one. - * - * Push the new key_def as cdata to a Lua stack. + * key_def:merge(second_key_def) + * Stack: [1] key_def; [2] second_key_def. */ static int +lbox_key_def_merge(struct lua_State *L) +{ + int idx_a = 1; + int idx_b = 2; + if (lua_gettop(L) != 2 || luaT_is_key_def(L, idx_a) == NULL || + luaT_is_key_def(L, idx_b) == NULL) + return luaL_error(L, "Usage: key_def:merge(second_key_def)"); + return luaT_key_def_merge(L, idx_a, idx_b); +} + +/** + * Push a new table representing a key_def to a Lua stack. + */ +static int +lbox_key_def_to_table(struct lua_State *L) +{ + struct key_def *key_def = luaT_is_key_def(L, 1); + if (lua_gettop(L) != 1 || key_def == NULL) + return luaL_error(L, "Usage: key_def:totable()"); + luaT_push_key_def_parts(L, key_def); + return 1; +} + +int lbox_key_def_new(struct lua_State *L) { if (lua_gettop(L) != 1 || lua_istable(L, 1) != 1) @@ -493,11 +522,7 @@ lbox_key_def_new(struct lua_State *L) */ key_def_update_optionality(key_def, 0); - *(struct key_def **) luaL_pushcdata(L, - CTID_STRUCT_KEY_DEF_REF) = key_def; - lua_pushcfunction(L, lbox_key_def_gc); - luaL_setcdatagc(L, -2); - + luaT_push_key_def(L, key_def); return 1; } diff --git a/src/box/lua/key_def.h b/src/box/lua/key_def.h index 88b8a0019231f0b1d7f4d40c29937f31645b3103..d7035d0f0810fbf9424a7cca00d04629c154c9f9 100644 --- a/src/box/lua/key_def.h +++ b/src/box/lua/key_def.h @@ -38,6 +38,13 @@ extern "C" { struct lua_State; struct key_def; +/** + * Push a copy of key_def as a cdata object to a Lua stack, and set finalizer + * function lbox_key_def_gc for it. + */ +void +luaT_push_key_def(struct lua_State *L, const struct key_def *key_def); + /** * Push a new table representing a key_def to a Lua stack. * Table is consists of key_def::parts tables that describe @@ -46,7 +53,7 @@ struct key_def; * object doesn't declare them where not necessary. */ void -luaT_push_key_def(struct lua_State *L, const struct key_def *key_def); +luaT_push_key_def_parts(struct lua_State *L, const struct key_def *key_def); /** * Check key_def pointer in LUA stack by specified index. @@ -54,7 +61,62 @@ luaT_push_key_def(struct lua_State *L, const struct key_def *key_def); * Returns not NULL tuple pointer on success, NULL otherwise. */ struct key_def * -luaT_check_key_def(struct lua_State *L, int idx); +luaT_is_key_def(struct lua_State *L, int idx); + +/** + * Extract key from tuple by given key definition and return + * tuple representing this key. + * Push the new key tuple as cdata to a LUA stack on success. + * Raise error otherwise. + */ +int +luaT_key_def_extract_key(struct lua_State *L, int idx); + +/** + * Compare tuples using the key definition. + * Push 0 if key_fields(tuple_a) == key_fields(tuple_b) + * <0 if key_fields(tuple_a) < key_fields(tuple_b) + * >0 if key_fields(tuple_a) > key_fields(tuple_b) + * integer to a LUA stack on success. + * Raise error otherwise. + */ +int +luaT_key_def_compare(struct lua_State *L, int idx); + +/** + * Compare tuple with key using the key definition. + * Push 0 if key_fields(tuple) == parts(key) + * <0 if key_fields(tuple) < parts(key) + * >0 if key_fields(tuple) > parts(key) + * integer to a LUA stack on success. + * Raise error otherwise. + */ +int +luaT_key_def_compare_with_key(struct lua_State *L, int idx); + +/** + * Construct and export to LUA a new key definition with a set + * union of key parts from first and second key defs. Parts of + * the new key_def consist of the first key_def's parts and those + * parts of the second key_def that were not among the first + * parts. + * Push the new key_def as cdata to a LUA stack on success. + * Raise error otherwise. + */ +int +luaT_key_def_merge(struct lua_State *L, int idx_a, int idx_b); + +/** + * Create a new key_def from a Lua table. + * + * Expected a table of key parts on the Lua stack. The format is + * the same as box.space.<...>.index.<...>.parts or corresponding + * net.box's one. + * + * Push the new key_def as cdata to a Lua stack. + */ +int +lbox_key_def_new(struct lua_State *L); /** * Register the module. diff --git a/src/box/lua/merger.c b/src/box/lua/merger.c index fb860dce580aa3cea4b6352eeb1e3d469d7e3ab5..bbe8ca3d02ac6ba3b67d013e5553f0d8f16531e7 100644 --- a/src/box/lua/merger.c +++ b/src/box/lua/merger.c @@ -50,7 +50,7 @@ #include "lua/utils.h" /* luaL_pushcdata(), luaL_iterator_*() */ -#include "box/lua/key_def.h" /* luaT_check_key_def() */ +#include "box/lua/key_def.h" /* luaT_is_key_def() */ #include "box/lua/tuple.h" /* luaT_tuple_new() */ #include "small/ibuf.h" /* struct ibuf */ @@ -352,7 +352,7 @@ lbox_merger_new(struct lua_State *L) int top = lua_gettop(L); bool ok = (top == 2 || top == 3) && /* key_def. */ - (key_def = luaT_check_key_def(L, 1)) != NULL && + (key_def = luaT_is_key_def(L, 1)) != NULL && /* Sources. */ lua_istable(L, 2) == 1 && /* Opts. */ diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc index 688f6b969e7d0ff0a5aa89f0e1c5a5cbf71a24bd..8896cf94f9023dc62122731f18205d201151db28 100644 --- a/src/box/lua/space.cc +++ b/src/box/lua/space.cc @@ -279,6 +279,123 @@ lbox_push_space_foreign_key(struct lua_State *L, struct space *space, int i) lua_setfield(L, i, "foreign_key"); } +/** + * index.parts:extract_key(tuple) + * Stack: [1] unused; [2] tuple. + * key_def is passed in the upvalue. + */ +static int +lbox_index_parts_extract_key(struct lua_State *L) +{ + if (lua_gettop(L) != 2) + return luaL_error(L, "Usage: index.parts:extract_key(tuple)"); + return luaT_key_def_extract_key(L, lua_upvalueindex(1)); +} + +/** + * index.parts:compare(tuple_a, tuple_b) + * Stack: [1] unused; [2] tuple_a; [3] tuple_b. + * key_def is passed in the upvalue. + */ +static int +lbox_index_parts_compare(struct lua_State *L) +{ + if (lua_gettop(L) != 3) { + return luaL_error(L, "Usage: index.parts:compare(" + "tuple_a, tuple_b)"); + } + return luaT_key_def_compare(L, lua_upvalueindex(1)); +} + +/** + * index.parts:compare_with_key(tuple, key) + * Stack: [1] unused; [2] tuple; [3] key. + * key_def is passed in the upvalue. + */ +static int +lbox_index_parts_compare_with_key(struct lua_State *L) +{ + if (lua_gettop(L) != 3) { + return luaL_error(L, "Usage: index.parts:compare_with_key(" + "tuple, key)"); + } + return luaT_key_def_compare_with_key(L, lua_upvalueindex(1)); +} + +/** + * index.parts:merge(second_index_parts) + * Stack: [1] unused; [2] second_index_parts. + * First key_def is passed in the upvalue. + */ +static int +lbox_index_parts_merge(struct lua_State *L) +{ + int idx_b; + struct key_def *key_def_b; + if (lua_gettop(L) != 2) { + return luaL_error(L, "Usage: index.parts:merge(" + "second_index_parts)"); + } + lua_pushcfunction(L, lbox_key_def_new); + lua_replace(L, 1); + /* + * Stack: + * [1] lbox_key_def_new + * [2] second_index_parts (first argument for lbox_key_def_new) + */ + if (lua_pcall(L, 1, 1, 0) != 0) + goto key_def_b_error; + /* + * Stack: + * [1] key_def_b + */ + idx_b = 1; + key_def_b = luaT_is_key_def(L, idx_b); + if (key_def_b == NULL) + goto key_def_b_error; + return luaT_key_def_merge(L, lua_upvalueindex(1), idx_b); + +key_def_b_error: + return luaL_error(L, + "Can't create key_def from the second index.parts"); +} + +/** + * Populate __index metamethod of index_object.parts table with the methods, + * which work like require('key_def').new(index_object.parts) methods. + * Each method is implemented as a C closure, associated with `struct key_def'. + */ +static void +luaT_add_index_parts_methods(struct lua_State *L, const struct key_def *key_def) +{ + /* Metatable. */ + lua_newtable(L); + /* __index */ + lua_newtable(L); + int idx_index = lua_gettop(L); + + /* Push 4 references to cdata onto the stack, one for each closure. */ + luaT_push_key_def(L, key_def); + lua_pushvalue(L, -1); + lua_pushvalue(L, -1); + lua_pushvalue(L, -1); + + lua_pushcclosure(L, &lbox_index_parts_extract_key, 1); + lua_setfield(L, idx_index, "extract_key"); + + lua_pushcclosure(L, &lbox_index_parts_compare, 1); + lua_setfield(L, idx_index, "compare"); + + lua_pushcclosure(L, &lbox_index_parts_compare_with_key, 1); + lua_setfield(L, idx_index, "compare_with_key"); + + lua_pushcclosure(L, &lbox_index_parts_merge, 1); + lua_setfield(L, idx_index, "merge"); + + lua_setfield(L, -2, "__index"); + lua_setmetatable(L, -2); +} + /** * Make a single space available in Lua, * via box.space[] array. @@ -452,8 +569,8 @@ lbox_fillspace(struct lua_State *L, struct space *space, int i) lua_setfield(L, -2, "name"); lua_pushstring(L, "parts"); - luaT_push_key_def(L, index_def->key_def); - + luaT_push_key_def_parts(L, index_def->key_def); + luaT_add_index_parts_methods(L, index_def->key_def); lua_settable(L, -3); /* space.index[k].parts */ lua_pushstring(L, "sequence_id"); diff --git a/test/box-luatest/gh_7356_index_parts_methods_test.lua b/test/box-luatest/gh_7356_index_parts_methods_test.lua new file mode 100644 index 0000000000000000000000000000000000000000..1ea3c26c7a8ca735cb415ced56bfb34b939a7229 --- /dev/null +++ b/test/box-luatest/gh_7356_index_parts_methods_test.lua @@ -0,0 +1,161 @@ +local t = require('luatest') +local g = t.group('gh-7356') + +g.before_all(function(cg) + local server = require('luatest.server') + cg.server = server:new({alias = 'gh_7356'}) + cg.server:start() +end) + +g.after_all(function(cg) + cg.server:drop() +end) + +g.after_each(function(cg) + cg.server:exec(function() + if box.space.test then box.space.test:drop() end + end) +end) + +-- Test index_object.parts:extract_key(tuple) +g.test_extract_key = function(cg) + cg.server:exec(function() + local s = box.schema.space.create('test') + local pk = s:create_index('pk') + s:insert{1, 99.5, 'X', nil, {'a', 'b'}} + local sk = s:create_index('sk', {parts = {3, 'string', 1, 'unsigned'}}) + local k = sk.parts:extract_key(pk:get{1}) + t.assert_equals(k, {'X', 1}) + + t.assert_error_msg_content_equals( + 'Usage: index.parts:extract_key(tuple)', + sk.parts.extract_key) + t.assert_error_msg_content_equals( + 'Usage: index.parts:extract_key(tuple)', + sk.parts.extract_key, sk.parts) + t.assert_error_msg_content_equals( + 'A tuple or a table expected, got number', + sk.parts.extract_key, sk.parts, 0) + t.assert_error_msg_content_equals( + 'Tuple field [3] required by space format is missing', + sk.parts.extract_key, sk.parts, {0}) + + local mk = s:create_index('mk', {parts = {{path = '[*]', field = 5}}}) + t.assert_error_msg_content_equals( + 'multikey path is unsupported', + mk.parts.extract_key, mk.parts, pk:get{1}) + + -- Check that extract_key() method is recreated with the correct key_def + -- object after alter(). + sk:alter({parts = {1, 'unsigned', 2, 'double'}}) + t.assert_equals(sk.parts:extract_key(pk:get{1}), {1, 99.5}) + end) +end + +-- Test index_object.parts:compare(tuple_a, tuple_b) +g.test_compare = function(cg) + cg.server:exec(function() + local s = box.schema.space.create('test') + local i = s:create_index('i', {parts = { + {field = 3, type = 'string', collation = 'unicode_ci'}, + {field = 1, type = 'unsigned'} + }}) + local tuple_a = {1, 99.5, 'x', nil, {'a', 'b'}} + local tuple_b = {1, 99.5, 'X', nil, {'a', 'b'}} + local tuple_c = {2, 99.5, 'X', nil, {'a', 'b'}} + t.assert_equals(i.parts:compare(tuple_a, tuple_b), 0) + t.assert_equals(i.parts:compare(tuple_a, tuple_c), -1) + t.assert_equals(i.parts.compare(nil, tuple_c, tuple_a), 1) + + t.assert_error_msg_content_equals( + 'Usage: index.parts:compare(tuple_a, tuple_b)', + i.parts.compare) + t.assert_error_msg_content_equals( + 'Usage: index.parts:compare(tuple_a, tuple_b)', + i.parts.compare, i.parts, tuple_a) + t.assert_error_msg_content_equals( + 'A tuple or a table expected, got cdata', + i.parts.compare, i.parts, tuple_a, box.NULL) + t.assert_error_msg_content_equals( + 'Supplied key type of part 0 does not match index part type: ' .. + 'expected string', i.parts.compare, nil, {0, [3]=0}, tuple_b) + + local mk = s:create_index('mk', {parts = {{path = '[*]', field = 5}}}) + t.assert_error_msg_content_equals( + 'multikey path is unsupported', + mk.parts.compare, mk.parts, tuple_a, tuple_b) + end) +end + +-- Test index_object.parts:compare_with_key(tuple_a, tuple_b) +g.test_compare_with_key = function(cg) + cg.server:exec(function() + local s = box.schema.space.create('test') + local i = s:create_index('i', {parts = { + {field = 3, type = 'string', collation = 'unicode_ci'}, + {field = 1, type = 'unsigned'} + }}) + local tuple = {1, 99.5, 'x', nil, {'a', 'b'}} + t.assert_equals(i.parts:compare_with_key(tuple, {'x', 1}), 0) + t.assert_equals(i.parts:compare_with_key(tuple, {'X', 1}), 0) + t.assert_equals(i.parts:compare_with_key(tuple, {'X', 2}), -1) + + t.assert_error_msg_content_equals( + 'Usage: index.parts:compare_with_key(tuple, key)', + i.parts.compare_with_key) + t.assert_error_msg_content_equals( + 'Usage: index.parts:compare_with_key(tuple, key)', + i.parts.compare_with_key, box.NULL) + t.assert_error_msg_content_equals( + 'Usage: index.parts:compare_with_key(tuple, key)', + i.parts.compare_with_key, box.NULL, {0, nil, ''}) + t.assert_error_msg_content_equals( + 'Supplied key type of part 1 does not match index part type: ' .. + 'expected unsigned', + i.parts.compare_with_key, box.NULL, {0, nil, ''}, {'', ''}) + + local mk = s:create_index('mk', {parts = {{path = '[*]', field = 5}}}) + t.assert_error_msg_content_equals( + 'multikey path is unsupported', + mk.parts.compare_with_key, mk.parts, tuple, {'x', 1}) + end) +end + +-- Test index_object.parts:merge(second_index_parts) +g.test_merge = function(cg) + cg.server:exec(function() + local s = box.schema.space.create('test') + local i = s:create_index('i', {parts = {{type = 'string', field = 3}, + {type = 'scalar', field = 2}}}) + local j = s:create_index('j', {parts = {{type = 'unsigned', field = 1}, + {type = 'string', field = 3}}}) + local exp = {{fieldno = 3, type = 'string', is_nullable = false}, + {fieldno = 2, type = 'scalar', is_nullable = false}, + {fieldno = 1, type = 'unsigned', is_nullable = false}} + t.assert_equals(i.parts:merge(j.parts):totable(), exp) + + t.assert_error_msg_content_equals( + 'Usage: index.parts:merge(second_index_parts)', + i.parts.merge) + t.assert_error_msg_content_equals( + 'Usage: index.parts:merge(second_index_parts)', + i.parts.merge, i.parts) + t.assert_error_msg_content_equals( + 'Usage: index.parts:merge(second_index_parts)', + i.parts.merge, i.parts, j.parts, box.NULL) + t.assert_error_msg_content_equals( + "Can't create key_def from the second index.parts", + i.parts.merge, i.parts, 100) + t.assert_error_msg_content_equals( + "Can't create key_def from the second index.parts", + i.parts.merge, i.parts, {{type = 'scalar'}}) + + local mk = s:create_index('mk', {parts = {{path = '[*]', field = 5}}}) + t.assert_error_msg_content_equals( + 'multikey path is unsupported', + mk.parts.merge, mk.parts, j.parts) + t.assert_error_msg_content_equals( + "Can't create key_def from the second index.parts", + i.parts.merge, i.parts, mk.parts) + end) +end diff --git a/test/box-tap/key_def.test.lua b/test/box-tap/key_def.test.lua index 4f101b7aa440c68dd3f39f546ca0587da989afc5..1b89dea1960af98ca9e25d2ddaeabb1f5c644f3a 100755 --- a/test/box-tap/key_def.test.lua +++ b/test/box-tap/key_def.test.lua @@ -182,7 +182,7 @@ local key_def_new_cases = { local test = tap.test('key_def') -test:plan(#key_def_new_cases - 1 + 7) +test:plan(#key_def_new_cases - 1 + 8) for _, case in ipairs(key_def_new_cases) do if type(case) == 'function' then case() @@ -557,4 +557,25 @@ test:test('merge()', function(test) 'composite case') end) +-- Check the usage error messages. +test:test('Usage errors', function(test) + test:plan(5) + -- key_def_lib.new() is tested above. + test:is_deeply({pcall(key_def_lib.extract_key)}, + {false, 'Usage: key_def:extract_key(tuple)'}, + 'extract_key()') + test:is_deeply({pcall(key_def_lib.compare)}, + {false, 'Usage: key_def:compare(tuple_a, tuple_b)'}, + 'compare()') + test:is_deeply({pcall(key_def_lib.compare_with_key)}, + {false, 'Usage: key_def:compare_with_key(tuple, key)'}, + 'compare_with_key()') + test:is_deeply({pcall(key_def_lib.merge)}, + {false, 'Usage: key_def:merge(second_key_def)'}, + 'merge()') + test:is_deeply({pcall(key_def_lib.totable)}, + {false, 'Usage: key_def:totable()'}, + 'totable()') +end) + os.exit(test:check() and 0 or 1)