diff --git a/extra/exports b/extra/exports index b4330ea90d9a734bc6ff8b9d91335ac31ddce21c..22439ee69f1bbaea585897b8f0dc526fe9e9e4b7 100644 --- a/extra/exports +++ b/extra/exports @@ -127,6 +127,7 @@ box_tuple_format_default box_tuple_format_new box_tuple_format_ref box_tuple_format_unref +box_tuple_hash box_tuple_iterator box_tuple_iterator_free box_tuple_new diff --git a/src/box/key_def.c b/src/box/key_def.c index f2e6c65f2e9a1bd9323c84e8e7ac27229c7ef267..52771e6b78e6a9f1e41f11a15c1cdf2f05994434 100644 --- a/src/box/key_def.c +++ b/src/box/key_def.c @@ -632,6 +632,12 @@ box_tuple_compare_with_key(box_tuple_t *tuple_a, const char *key_b, } +uint32_t +box_tuple_hash(box_tuple_t *tuple, box_key_def_t *key_def) +{ + return tuple_hash(tuple, key_def); +} + box_key_def_t * box_key_def_merge(const box_key_def_t *first, const box_key_def_t *second) { diff --git a/src/box/key_def.h b/src/box/key_def.h index 03f71796626ef46d80856c4de4d1134e0947112e..42237374aec7f4816caa2d05d96b9f0cfa0cd22a 100644 --- a/src/box/key_def.h +++ b/src/box/key_def.h @@ -539,6 +539,16 @@ API_EXPORT int box_tuple_compare_with_key(box_tuple_t *tuple_a, const char *key_b, box_key_def_t *key_def); +/** + * Calculate a tuple hash value with a given key definition. + * + * @param tuple tuple + * @param key_def key definition + * @return - hash value + */ +API_EXPORT uint32_t +box_tuple_hash(box_tuple_t *tuple, box_key_def_t *key_def); + /** * Allocate a new key_def with a set union of key parts from * first and second key defs. diff --git a/src/box/lua/key_def.c b/src/box/lua/key_def.c index 14cb770fc924994065996605a132275872f82482..a95431de2fb755d45d714756b8aa2eff8a4b4358 100644 --- a/src/box/lua/key_def.c +++ b/src/box/lua/key_def.c @@ -398,6 +398,33 @@ lbox_key_def_compare_with_key(struct lua_State *L) return 1; } +/** + * Calculate a tuple hash with a given key definition. + * At the moment 32-bit murmur3 hash is used but it may + * change in future. + * Push hash integer to a LUA stack on success. + * Raise error otherwise. + */ +static int +lbox_key_def_hash(struct lua_State *L) +{ + struct key_def *key_def = luaT_check_key_def(L, 1); + if (lua_gettop(L) != 2 || key_def == NULL) + return luaL_error(L, "Usage: key_def:hash(tuple)"); + + 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); + uint32_t hash = box_tuple_hash(tuple, key_def); + region_truncate(region, region_svp); + tuple_unref(tuple); + lua_pushinteger(L, hash); + 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 @@ -519,6 +546,7 @@ luaopen_key_def(struct lua_State *L) {"extract_key", lbox_key_def_extract_key}, {"compare", lbox_key_def_compare}, {"compare_with_key", lbox_key_def_compare_with_key}, + {"hash", lbox_key_def_hash}, {"merge", lbox_key_def_merge}, {"totable", lbox_key_def_to_table}, {NULL, NULL} diff --git a/src/box/lua/key_def.lua b/src/box/lua/key_def.lua index 97905eebfba8eefdd4a34662cb98bfc4cf443b39..ce73b41c90624e34be5158471c159fca4f9e46fb 100644 --- a/src/box/lua/key_def.lua +++ b/src/box/lua/key_def.lua @@ -6,6 +6,7 @@ local methods = { ['extract_key'] = key_def.extract_key, ['compare'] = key_def.compare, ['compare_with_key'] = key_def.compare_with_key, + ['hash'] = key_def.hash, ['merge'] = key_def.merge, ['totable'] = key_def.totable, ['__serialize'] = key_def.totable, diff --git a/test/app-tap/module_api.c b/test/app-tap/module_api.c index 2068f8e0dca76ba84a2f6937f9d382506025160c..8c8a89d0adff22da13c7d63e7e17022103b0b34e 100644 --- a/test/app-tap/module_api.c +++ b/test/app-tap/module_api.c @@ -1973,6 +1973,72 @@ test_key_def_validate_key(struct lua_State *L) return 1; } +static int +test_key_def_hash(struct lua_State *L) +{ + size_t region_svp = box_region_used(); + + /* Prepare a new tuple [1, 2, 3] */ + box_tuple_t *tuple_1 = new_runtime_tuple("\x93\x01\x02\x03", 4); + /* Create a key definition from the first two columns. */ + box_key_part_def_t parts_1[2]; + box_key_part_def_create(&parts_1[0]); + box_key_part_def_create(&parts_1[1]); + parts_1[0].fieldno = 0; + parts_1[0].field_type = "integer"; + parts_1[1].fieldno = 1; + parts_1[1].field_type = "integer"; + box_key_def_t *key_def_1 = box_key_def_new_v2(parts_1, 2); + fail_unless(key_def_1 != NULL); + /* Calculate tuple's hash */ + uint32_t hash_1 = box_tuple_hash(tuple_1, key_def_1); + fail_unless(hash_1 == 605624609); + /* Clean up */ + box_key_def_delete(key_def_1); + box_tuple_unref(tuple_1); + + /* Prepare a new tuple [1] */ + box_tuple_t *tuple_2 = new_runtime_tuple("\x91\x01", 2); + /* Create a key definition from its column. */ + box_key_part_def_t parts_2[1]; + box_key_part_def_create(&parts_2[0]); + parts_2[0].fieldno = 0; + parts_2[0].field_type = "integer"; + box_key_def_t *key_def_2 = box_key_def_new_v2(parts_2, 1); + fail_unless(key_def_2 != NULL); + /* Calculate tuple's hash */ + uint32_t hash_2 = box_tuple_hash(tuple_2, key_def_2); + fail_unless(hash_2 == 1457374933); + /* Clean up */ + box_key_def_delete(key_def_2); + box_tuple_unref(tuple_2); + + /* Prepare a new tuple [1] */ + box_tuple_t *tuple_3 = new_runtime_tuple("\x91\x01", 2); + /* Create a key defenition where the second column is nullable */ + box_key_part_def_t parts_3[2]; + box_key_part_def_create(&parts_3[0]); + box_key_part_def_create(&parts_3[1]); + parts_3[0].fieldno = 0; + parts_3[0].field_type = "integer"; + parts_3[1].fieldno = 1; + parts_3[1].field_type = "integer"; + parts_3[1].flags = BOX_KEY_PART_DEF_IS_NULLABLE; + box_key_def_t *key_def_3 = box_key_def_new_v2(parts_3, 2); + fail_unless(key_def_3 != NULL); + /* Calculate tuple's hash */ + uint32_t hash_3 = box_tuple_hash(tuple_3, key_def_3); + fail_unless(hash_3 == 766361540); + /* Clean up */ + box_key_def_delete(key_def_3); + box_tuple_unref(tuple_3); + + box_region_truncate(region_svp); + + lua_pushboolean(L, 1); + return 1; +} + static int test_key_def_dup(lua_State *L) { @@ -3197,6 +3263,7 @@ luaopen_module_api(lua_State *L) {"test_key_def_merge", test_key_def_merge}, {"test_key_def_extract_key", test_key_def_extract_key}, {"test_key_def_validate_key", test_key_def_validate_key}, + {"test_key_def_hash", test_key_def_hash}, {"test_box_ibuf", test_box_ibuf}, {"tuple_validate_def", test_tuple_validate_default}, {"tuple_validate_fmt", test_tuple_validate_formatted}, diff --git a/test/app-tap/module_api.test.lua b/test/app-tap/module_api.test.lua index 76303a24ecbc03ad3db8d4d8adf25fcdf9d18dbb..5642b9a59b2d0b656ca235326db4a34155f98487 100755 --- a/test/app-tap/module_api.test.lua +++ b/test/app-tap/module_api.test.lua @@ -664,7 +664,7 @@ local function test_box_iproto_override(test, module) end require('tap').test("module_api", function(test) - test:plan(49) + test:plan(50) local status, module = pcall(require, 'module_api') test:is(status, true, "module") test:ok(status, "module is loaded") diff --git a/test/box-luatest/key_def_test.lua b/test/box-luatest/key_def_test.lua new file mode 100644 index 0000000000000000000000000000000000000000..80dbd9e73c7c1ffe1751592573bd5d4b6c2a60a5 --- /dev/null +++ b/test/box-luatest/key_def_test.lua @@ -0,0 +1,25 @@ +local t = require('luatest') + +local g = t.group('tuple_hash') + +g.test_key_def_tuple_hash = function() + local key_def = require('key_def') + local tuple = box.tuple.new({1, 2, 3}) + + local def = key_def.new({ + {fieldno = 1, type = 'integer'}, + {fieldno = 2, type = 'integer'} + }) + t.assert_equals(def:hash(tuple), 605624609) + + def = key_def.new({{fieldno = 1, type = 'integer'}}) + tuple = box.tuple.new({1}) + t.assert_equals(def:hash(tuple), 1457374933) + + def = key_def.new({ + {fieldno = 1, type = 'integer'}, + {fieldno = 2, type = 'integer', is_nullable = true} + }) + tuple = box.tuple.new({1}) + t.assert_equals(def:hash(tuple), 766361540) +end