diff --git a/doc/sphinx/book/box/authentication.rst b/doc/sphinx/book/box/authentication.rst index 8adeee8b3b080c86c3b1966c04987b8c3fd271ae..fee93d092a418eb8cccdb457dc56d76c751c31a9 100644 --- a/doc/sphinx/book/box/authentication.rst +++ b/doc/sphinx/book/box/authentication.rst @@ -193,7 +193,7 @@ The function for granting a privilege is: .. cssclass:: highlight .. parsed-literal:: - box.schema.user.grant(*grantee*, *operation*, *object-type*, *obejct-name*[, *options*]) + box.schema.user.grant(*grantee*, *operation*, *object-type*, *object-name*[, *options*]) -- OR box.schema.user.grant(*grantee*, *operation*, 'universe' [, nil, *options*]) diff --git a/doc/sphinx/book/box/box_space.rst b/doc/sphinx/book/box/box_space.rst index 2b6377f24365c95bce98ca6392edb16894e58832..e627f984b1ff6301e911852a51b93e0eccfb07c1 100644 --- a/doc/sphinx/book/box/box_space.rst +++ b/doc/sphinx/book/box/box_space.rst @@ -78,6 +78,7 @@ A list of all ``box.space`` functions follows, then comes a list of all | :class:`box.space._cluster` | .(Metadata) List of clusters | +---------------------------------------------------------------------+---------------------------------+ + .. module:: box.space .. class:: space_object @@ -548,6 +549,7 @@ A list of all ``box.space`` functions follows, then comes a list of all :return: null. Possible errors: it is illegal to modify a primary-key field. + It is illegal to use upsert with a space that has a unique secondary index. **Complexity factors:** Index size, Index type, number of indexes accessed, WAL settings. diff --git a/doc/sphinx/reference/digest.rst b/doc/sphinx/reference/digest.rst index 075f34de502a7ca0201ec4aa33b3f1834927af08..5e1bf02c668f5ea4f6df1351164eb9b20c5313d5 100644 --- a/doc/sphinx/reference/digest.rst +++ b/doc/sphinx/reference/digest.rst @@ -109,7 +109,7 @@ functions in digest are: .. _digest-guava: -.. function:: guaava(integer, integer) +.. function:: guava(integer, integer) Returns a number made with consistent hash. diff --git a/doc/sphinx/reference/shard.rst b/doc/sphinx/reference/shard.rst index a33033bc0a83f12c4496e7b484346661ed1fb03d..d24e2e7f79caaf4401b6f86dff47fb35196eac56 100644 --- a/doc/sphinx/reference/shard.rst +++ b/doc/sphinx/reference/shard.rst @@ -97,9 +97,9 @@ The shard package will conclude that there is only one shard. tarantool> cfg = { > servers = { - > { uri = 'loclhost:33131', zone = '1' }, - > { uri = 'loclhost:33132', zone = '2' }, - > { uri = 'loclhost:33133', zone = '3' },1 + > { uri = 'localhost:33131', zone = '1' }, + > { uri = 'localhost:33132', zone = '2' }, + > { uri = 'localhost:33133', zone = '3' },1 > }, > login = 'tester', > password = 'pass', @@ -125,13 +125,13 @@ necessarily an error, because perhaps one of the servers in the list is not aliv tarantool> cfg = { > servers = { - > { uri = 'loclhost:33131', zone = '1' }, - > { uri = 'loclhost:33131', zone = '2' }, - > { uri = 'loclhost:33132', zone = '1' }, - > { uri = 'loclhost:33133', zone = '2' }, - > { uri = 'loclhost:33131', zone = '1' }, - > { uri = 'loclhost:33132', zone = '2' }, - > { uri = 'loclhost:33133', zone = '1' }, + > { uri = 'localhost:33131', zone = '1' }, + > { uri = 'localhost:33131', zone = '2' }, + > { uri = 'localhost:33132', zone = '1' }, + > { uri = 'localhost:33133', zone = '2' }, + > { uri = 'localhost:33131', zone = '1' }, + > { uri = 'localhost:33132', zone = '2' }, + > { uri = 'localhost:33133', zone = '1' }, > }, > login = 'tester', > password = 'pass', diff --git a/doc/sphinx/reference/socket.rst b/doc/sphinx/reference/socket.rst index 4c9a9bf1b31221aabdc74f8ba9d14a85b161084e..db571d1ffd2f96c43d35d866210aee32e7d4d748 100644 --- a/doc/sphinx/reference/socket.rst +++ b/doc/sphinx/reference/socket.rst @@ -27,8 +27,8 @@ are ``errno``, ``error``. | Purposes | Names | +================+===============================================================+ | setup | :ref:`socket() <socket-socket>` | - | +---------------------------------------------------------------+ - | | :func:`socket.tcp_connect() <socket.tcp_connect>` | + +----------------+---------------------------------------------------------------+ + | "" | :func:`socket.tcp_connect() <socket.tcp_connect>` | +----------------+---------------------------------------------------------------+ | "" | :func:`socket.tcp_server() <socket.tcp_server>` | +----------------+---------------------------------------------------------------+ diff --git a/doc/sphinx/reference/tarantool.rst b/doc/sphinx/reference/tarantool.rst index 88bab8c2ff6538098436f79c3544512de8e05d82..770f670795a6fb52ffc7f659a030b4a368271e34 100644 --- a/doc/sphinx/reference/tarantool.rst +++ b/doc/sphinx/reference/tarantool.rst @@ -25,7 +25,7 @@ the tarantool package is recommended. --- - build: target: Linux-x86_64-RelWithDebInfo - options: cmake . -DCMAKE_INSTALL_PREFIX=/usr -DENABLE_TRACE=ON -DENABLE_BACKTRACE=ON + options: cmake . -DCMAKE_INSTALL_PREFIX=/usr -DENABLE_BACKTRACE=ON mod_format: so flags: ' -fno-common -fno-omit-frame-pointer -fno-stack-protector -fexceptions -funwind-tables -fopenmp -msse2 -std=c11 -Wall -Wextra -Wno-sign-compare -Wno-strict-aliasing diff --git a/src/box/sophia_index.cc b/src/box/sophia_index.cc index 43c9cf94cc1023728892ccb021aa6efb2f705c19..cb7c3df8700b5f9fd767276a8a225d5ceba32eb2 100644 --- a/src/box/sophia_index.cc +++ b/src/box/sophia_index.cc @@ -413,7 +413,7 @@ sophia_upsert_mp(char **tuple, int *tuple_size_key, struct key_def *key_def, mp_keysize += mp_sizeof_uint(load_u64(key[i])); i++; } - *tuple_size_key = mp_keysize + mp_sizeof_array(key_def->part_count); + *tuple_size_key = mp_keysize; /* count fields */ int count = key_def->part_count; @@ -474,14 +474,20 @@ sophia_upsert(char **result, sophia_mempool_free(&alloc); return -1; } + + /* skip array size and key */ + const char *ptr = up; + mp_decode_array(&ptr); + ptr += tuple_size_key; + /* get new value */ - int size = up_size - tuple_size_key; + int size = (int)((up + up_size) - ptr); *result = (char *)malloc(size); if (! *result) { sophia_mempool_free(&alloc); return -1; } - memcpy(*result, up + tuple_size_key, size); + memcpy(*result, ptr, size); sophia_mempool_free(&alloc); return size; } diff --git a/src/lua/fio.c b/src/lua/fio.c index 32b26e7fa0927a02462b256e7fb60b661ad79b35..90281805960ec70bb6c4ffde65489fb790586ac7 100644 --- a/src/lua/fio.c +++ b/src/lua/fio.c @@ -486,7 +486,7 @@ lbox_fio_mkdir(struct lua_State *L) if (top >= 2) mode = lua_tointeger(L, 2); else - mode = 0; + mode = 0777; lua_pushboolean(L, coeio_mkdir(pathname, mode) == 0); return 1; } diff --git a/src/lua/ipc.c b/src/lua/ipc.c index 244ab2c17213330527f119f39ceafbb53f8dc679..3be7f56816c618cb30e04df6f1850fb2272d0327 100644 --- a/src/lua/ipc.c +++ b/src/lua/ipc.c @@ -52,14 +52,16 @@ lbox_ipc_channel(struct lua_State *L) { lua_Integer size = 0; - if (lua_gettop(L) > 0) { - if (lua_gettop(L) != 1 || !lua_isnumber(L, 1)) - luaL_error(L, "fiber.channel(size): bad arguments"); - + if (lua_isnoneornil(L, 1)) { + size = 0; + } else if (lua_isnumber(L, 1)) { size = lua_tointeger(L, -1); if (size < 0) luaL_error(L, "fiber.channel(size): negative size"); + } else { + luaL_error(L, "fiber.channel(size): bad arguments"); } + struct ipc_channel *ch = (struct ipc_channel *) lua_newuserdata(L, ipc_channel_memsize(size)); if (ch == NULL) @@ -73,17 +75,13 @@ lbox_ipc_channel(struct lua_State *L) } static inline struct ipc_channel * -lbox_check_channel(struct lua_State *L, int narg, const char *source) +lbox_check_channel(struct lua_State *L, int index, const char *source) { - void *ch; - int top = lua_gettop(L); - - if (narg > top || top + narg < 0 || - (ch = luaL_checkudata(L, narg, ipc_lib)) == NULL) { - + assert(index > 0); + if (index > lua_gettop(L)) luaL_error(L, "usage: %s", source); - } - return (struct ipc_channel *) ch; + /* Note: checkudata errs on mismatch, no point in checking res */ + return (struct ipc_channel *) luaL_checkudata(L, index, ipc_lib); } static int @@ -99,7 +97,7 @@ lbox_ipc_channel_gc(struct lua_State *L) static int lbox_ipc_channel_is_full(struct lua_State *L) { - struct ipc_channel *ch = lbox_check_channel(L, -1, "channel:is_full()"); + struct ipc_channel *ch = lbox_check_channel(L, 1, "channel:is_full()"); lua_pushboolean(L, ipc_channel_is_full(ch)); return 1; } @@ -107,7 +105,7 @@ lbox_ipc_channel_is_full(struct lua_State *L) static int lbox_ipc_channel_is_empty(struct lua_State *L) { - struct ipc_channel *ch = lbox_check_channel(L, -1, + struct ipc_channel *ch = lbox_check_channel(L, 1, "channel:is_empty()"); lua_pushboolean(L, ipc_channel_is_empty(ch)); return 1; @@ -124,26 +122,25 @@ lua_ipc_value_destroy(struct ipc_msg *base) static int lbox_ipc_channel_put(struct lua_State *L) { + static const char usage[] = "channel:put(var [, timeout])"; int rc = -1; struct ipc_channel *ch = - lbox_check_channel(L, 1, "channel:put(var, [, timeout])"); + lbox_check_channel(L, 1, usage); ev_tstamp timeout; - switch (lua_gettop(L)) { - case 2: + /* val */ + if (lua_gettop(L) < 2) + luaL_error(L, "usage: %s", usage); + + /* timeout (optional) */ + if (lua_isnoneornil(L, 3)) { timeout = TIMEOUT_INFINITY; - break; - case 3: - if (lua_isnumber(L, -1)) { - timeout = lua_tonumber(L, -1); - if (timeout >= 0) - /** Leave var on top of the stack. */ - lua_pop(L, 1); - break; - } - /** Fall through. */ - default: - luaL_error(L, "usage: channel:put(var [, timeout])"); + } else if (lua_isnumber(L, 3)) { + timeout = lua_tonumber(L, 3); + if (timeout < 0) + luaL_error(L, "usage: %s", usage); + } else { + luaL_error(L, "usage: %s", usage); } struct ipc_value *value = ipc_value_new(); @@ -151,7 +148,7 @@ lbox_ipc_channel_put(struct lua_State *L) goto end; value->base.destroy = lua_ipc_value_destroy; - /* The referenced value is on top of the stack. */ + lua_pushvalue(L, 2); value->i = luaL_ref(L, LUA_REGISTRYINDEX); rc = ipc_channel_put_msg_timeout(ch, &value->base, timeout); @@ -173,23 +170,20 @@ lbox_ipc_channel_put(struct lua_State *L) static int lbox_ipc_channel_get(struct lua_State *L) { + static const char usage[] = "channel:get([timeout])"; struct ipc_channel *ch = - lbox_check_channel(L, 1, "channel:get([timeout])"); + lbox_check_channel(L, 1, usage); ev_tstamp timeout; - switch (lua_gettop(L)) { - case 1: + /* timeout (optional) */ + if (lua_isnoneornil(L, 2)) { timeout = TIMEOUT_INFINITY; - break; - case 2: - if (lua_isnumber(L, -1)) { - timeout = lua_tonumber(L, -1); - if (timeout >= 0) - break; - } - /** Fall through. */ - default: - luaL_error(L, "usage: channel:get([timeout])"); + } else if (lua_isnumber(L, 2)) { + timeout = lua_tonumber(L, 2); + if (timeout < 0) + luaL_error(L, "usage: %s", usage); + } else { + luaL_error(L, "usage: %s", usage); } struct ipc_value *value; @@ -213,7 +207,7 @@ lbox_ipc_channel_get(struct lua_State *L) static int lbox_ipc_channel_has_readers(struct lua_State *L) { - struct ipc_channel *ch = lbox_check_channel(L, -1, + struct ipc_channel *ch = lbox_check_channel(L, 1, "channel:has_readers()"); lua_pushboolean(L, ipc_channel_has_readers(ch)); return 1; @@ -222,7 +216,7 @@ lbox_ipc_channel_has_readers(struct lua_State *L) static int lbox_ipc_channel_has_writers(struct lua_State *L) { - struct ipc_channel *ch = lbox_check_channel(L, -1, + struct ipc_channel *ch = lbox_check_channel(L, 1, "channel:has_writers()"); lua_pushboolean(L, ipc_channel_has_writers(ch)); return 1; @@ -231,7 +225,7 @@ lbox_ipc_channel_has_writers(struct lua_State *L) static int lbox_ipc_channel_size(struct lua_State *L) { - struct ipc_channel *ch = lbox_check_channel(L, -1, "channel:size()"); + struct ipc_channel *ch = lbox_check_channel(L, 1, "channel:size()"); lua_pushinteger(L, ipc_channel_size(ch)); return 1; } @@ -239,7 +233,7 @@ lbox_ipc_channel_size(struct lua_State *L) static int lbox_ipc_channel_count(struct lua_State *L) { - struct ipc_channel *ch = lbox_check_channel(L, -1, "channel:count()"); + struct ipc_channel *ch = lbox_check_channel(L, 1, "channel:count()"); lua_pushinteger(L, ipc_channel_count(ch)); return 1; } diff --git a/test/app/fio.result b/test/app/fio.result index 4e115aa57356746e11ef9b467ee4f4756b342ae6..3378f7f5dd8c66943bdefe36113194b342d19b94 100644 --- a/test/app/fio.result +++ b/test/app/fio.result @@ -309,14 +309,32 @@ bit.band(fio.stat(file4).mode, 0x1FF) == 0x1F8 --- - true ... +dir1 = fio.pathjoin(tmpdir, 'dir1') +--- +... +dir2 = fio.pathjoin(tmpdir, 'dir2') +--- +... fio.mkdir(nil) --- - error: Usage fio.mkdir(pathname[, mode]) ... -fio.mkdir(fio.pathjoin(tmpdir, "dir")) +fio.mkdir(dir1) -- standard mode +--- +- true +... +fio.mkdir(dir2, 1) -- custom mode --- - true ... +string.format('%04o', bit.band(fio.stat(dir1).mode, 0x1FF)) +--- +- '0777' +... +string.format('%04o', bit.band(fio.stat(dir2).mode, 0x1FF)) +--- +- '0001' +... -- cleanup directories { fh1:close(), fh3:close() } --- @@ -334,7 +352,11 @@ fio.rmdir(nil) --- - error: 'Usage: fio.rmdir(pathname)' ... -fio.rmdir(fio.pathjoin(tmpdir, "dir")) +fio.rmdir(dir1) +--- +- true +... +fio.rmdir(dir2) --- - true ... diff --git a/test/app/fio.test.lua b/test/app/fio.test.lua index 2883c46ebd2a71f314e918a8a198bc12ec4e1faf..281e70d2203408abc24b4fb895f5890eb03447ae 100644 --- a/test/app/fio.test.lua +++ b/test/app/fio.test.lua @@ -103,15 +103,21 @@ bit.band(fh3:stat().mode, 0x1FF) == 0x1F8 bit.band(fio.stat(file4).mode, 0x1FF) == 0x1F8 +dir1 = fio.pathjoin(tmpdir, 'dir1') +dir2 = fio.pathjoin(tmpdir, 'dir2') fio.mkdir(nil) -fio.mkdir(fio.pathjoin(tmpdir, "dir")) +fio.mkdir(dir1) -- standard mode +fio.mkdir(dir2, 1) -- custom mode +string.format('%04o', bit.band(fio.stat(dir1).mode, 0x1FF)) +string.format('%04o', bit.band(fio.stat(dir2).mode, 0x1FF)) -- cleanup directories { fh1:close(), fh3:close() } { fh1:close(), errno.strerror(), fh3:close(), errno.strerror() } fio.rmdir(nil) -fio.rmdir(fio.pathjoin(tmpdir, "dir")) +fio.rmdir(dir1) +fio.rmdir(dir2) { fio.unlink(file1), fio.unlink(file2), fio.unlink(file3), fio.unlink(file4) } { fio.unlink(file1), fio.unlink(file2), fio.unlink(file3), fio.unlink(file4) } diff --git a/test/app/ipc.result b/test/app/ipc.result index 75683072e08030d10c7a9ebebc015cc836f9c240..fd242923239862c268f3129a35cb79342e6394e2 100644 --- a/test/app/ipc.result +++ b/test/app/ipc.result @@ -7,6 +7,10 @@ env = require('test_run') test_run = env.new() --- ... +-- channel methods ignore extra arguments, as regular Lua functions do +ignored_args = {'Extra', 'arguments', 'are', 'ignored'} +--- +... ch = fiber.channel(1) --- ... @@ -26,10 +30,34 @@ ch:is_empty() --- - true ... +ch:size(unpack(ignored_args)) +--- +- 1 +... +ch:count(unpack(ignored_args)) +--- +- 0 +... +ch:is_full(unpack(ignored_args)) +--- +- false +... +ch:is_empty(unpack(ignored_args)) +--- +- true +... ch:get(.1) --- - null ... +ch:get(.1, nil) +--- +- null +... +ch:get(.1, nil, unpack(ignored_args)) +--- +- null +... ch:put() --- - error: 'usage: channel:put(var [, timeout])' @@ -46,6 +74,16 @@ ch:get() --- - test ... +ch:put('test', nil), ch:get() +--- +- true +- test +... +ch:put('test', nil, unpack(ignored_args)), ch:get() +--- +- true +- test +... ch:get('wrong timeout') --- - error: 'usage: channel:get([timeout])' diff --git a/test/app/ipc.test.lua b/test/app/ipc.test.lua index 1e812d0a01753a188625c60f0185e1257acb9c13..36c467611c3e63a331bfdbe4ef012fda7dc77897 100644 --- a/test/app/ipc.test.lua +++ b/test/app/ipc.test.lua @@ -2,16 +2,27 @@ fiber = require('fiber') env = require('test_run') test_run = env.new() +-- channel methods ignore extra arguments, as regular Lua functions do +ignored_args = {'Extra', 'arguments', 'are', 'ignored'} + ch = fiber.channel(1) ch:size() ch:count() ch:is_full() ch:is_empty() +ch:size(unpack(ignored_args)) +ch:count(unpack(ignored_args)) +ch:is_full(unpack(ignored_args)) +ch:is_empty(unpack(ignored_args)) ch:get(.1) +ch:get(.1, nil) +ch:get(.1, nil, unpack(ignored_args)) ch:put() ch:count() ch:put('test') ch:get() +ch:put('test', nil), ch:get() +ch:put('test', nil, unpack(ignored_args)), ch:get() ch:get('wrong timeout') ch:get(-10) ch:put(234) diff --git a/test/sophia/gh.result b/test/sophia/gh.result index 9d735c79733cd53bfa8163ecc5a2e12f50f46d41..f2f5b005a736aa7c72c739f9396c437971aa306e 100644 --- a/test/sophia/gh.result +++ b/test/sophia/gh.result @@ -207,3 +207,80 @@ i:select({'1','2',nil},{iterator='GT'}) s:drop() --- ... +-- gh-1407: upsert generate garbage data +email_space_id = 'email' +--- +... +email_space = box.schema.space.create(email_space_id, { engine = 'sophia', if_not_exists = true }) +--- +... +i = email_space:create_index('primary', { parts = {1, 'STR'} }) +--- +... +time = 1234 +--- +... +email = "test@domain.com" +--- +... +email_hash_index = "asdfasdfs" +--- +... +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +--- +... +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +--- +... +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +--- +... +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +--- +... +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +--- +... +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +--- +... +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +--- +... +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +--- +... +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +--- +... +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +--- +... +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +--- +... +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +--- +... +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +--- +... +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +--- +... +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +--- +... +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +--- +... +box.space.email:select{email} +--- +- - ['test@domain.com', 'asdfasdfs', 1234, 'asdfasdfs', 1234, 'asdfasdfs', 1234, 'asdfasdfs', + 1234, 'asdfasdfs', 1234, 'asdfasdfs', 1234, 'asdfasdfs', 1234, 'asdfasdfs', 1234, + 'asdfasdfs', 1234, 'asdfasdfs', 1234, 'asdfasdfs', 1234, 'asdfasdfs', 1234, 'asdfasdfs', + 1234, 'asdfasdfs', 1234, 'asdfasdfs', 1234, 'asdfasdfs', 1234] +... +box.space.email:drop() +--- +... diff --git a/test/sophia/gh.test.lua b/test/sophia/gh.test.lua index 34408742ab57c47d38f1074d848205bdb9f536d0..44506bc6a746ec10b5d17bbceb364e1043ad57f7 100644 --- a/test/sophia/gh.test.lua +++ b/test/sophia/gh.test.lua @@ -88,3 +88,31 @@ s:insert{'1','2','3'} s:insert{'1','2','0'} i:select({'1','2',nil},{iterator='GT'}) s:drop() + + +-- gh-1407: upsert generate garbage data +email_space_id = 'email' +email_space = box.schema.space.create(email_space_id, { engine = 'sophia', if_not_exists = true }) +i = email_space:create_index('primary', { parts = {1, 'STR'} }) + +time = 1234 +email = "test@domain.com" +email_hash_index = "asdfasdfs" +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +box.space.email:upsert({email, email_hash_index, time}, {{'!', -1, email_hash_index}, {'!', -1, time}}) +box.space.email:select{email} +box.space.email:drop()