diff --git a/src/box/lua/tuple.cc b/src/box/lua/tuple.cc index a725236e26c9e6aba2b5177a2f183fc8fe6cde33..46968c4b02c37df996fed2af841db4d66eff18c9 100644 --- a/src/box/lua/tuple.cc +++ b/src/box/lua/tuple.cc @@ -433,60 +433,8 @@ lbox_pushtuple(struct lua_State *L, struct tuple *tuple) } } -/** - * Sequential access to tuple fields. Since tuple is a list-like - * structure, iterating over tuple fields is faster - * than accessing fields using an index. - */ -static int -lbox_tuple_next(struct lua_State *L) -{ - struct tuple *tuple = lua_checktuple(L, 1); - int argc = lua_gettop(L) - 1; - - struct tuple_iterator *it = NULL; - if (argc == 0 || (argc == 1 && lua_type(L, 2) == LUA_TNIL)) { - it = (struct tuple_iterator *) lua_newuserdata(L, sizeof(*it)); - assert(it != NULL); - luaL_getmetatable(L, tuple_iteratorlib_name); - lua_setmetatable(L, -2); - tuple_rewind(it, tuple); - } else if (argc == 1 && lua_type(L, 2) == LUA_TUSERDATA) { - it = (struct tuple_iterator *) - luaL_checkudata(L, 2, tuple_iteratorlib_name); - assert(it != NULL); - lua_pushvalue(L, 2); - } else { - return luaL_error(L, "tuple.next(): bad arguments"); - } - - const char *field = tuple_next(it); - if (field == NULL) { - lua_pop(L, 1); - lua_pushnil(L); - return 1; - } - - luamp_decode(L, &field); - return 2; -} - -/** Iterator over tuple fields. Adapt lbox_tuple_next - * to Lua iteration conventions. - */ -static int -lbox_tuple_pairs(struct lua_State *L) -{ - lua_pushcfunction(L, lbox_tuple_next); - lua_pushvalue(L, -2); /* tuple */ - lua_pushnil(L); - return 3; -} - static const struct luaL_reg lbox_tuple_meta[] = { {"__gc", lbox_tuple_gc}, - {"next", lbox_tuple_next}, - {"pairs", lbox_tuple_pairs}, {"slice", lbox_tuple_slice}, {"transform", lbox_tuple_transform}, {"find", lbox_tuple_find}, diff --git a/src/box/lua/tuple.lua b/src/box/lua/tuple.lua index 3527a5621e8b15bca60feaa20d66a1e342d3761c..febd79d06603b41f70f8c1ecdfca197d20a0f234 100644 --- a/src/box/lua/tuple.lua +++ b/src/box/lua/tuple.lua @@ -18,14 +18,76 @@ uint32_t tuple_arity(const struct tuple *tuple); const char * tuple_field(const struct tuple *tuple, uint32_t i); + +struct tuple_iterator { + const struct tuple *tuple; + const char *pos; + int fieldno; +}; + +void +tuple_rewind(struct tuple_iterator *it, const struct tuple *tuple); + +const char * +tuple_seek(struct tuple_iterator *it, uint32_t field_no); + +const char * +tuple_next(struct tuple_iterator *it); ]]) local builtin = ffi.C +local tuple_iterator_t = ffi.typeof('struct tuple_iterator') + +local function tuple_iterator_next(it, tuple, pos) + if pos == nil then + pos = 0 + elseif type(pos) ~= "number" then + error("error: invalid key to 'next'") + end + local field + if it.tuple == tuple and it.fieldno == pos then + -- Sequential iteration + print('optimization') + field = builtin.tuple_next(it) + else + -- Seek + builtin.tuple_rewind(it, tuple) + field = builtin.tuple_seek(it, pos); + end + if field == nil then + if #tuple == pos then + -- No more fields, stop iteration + return nil + else + -- Invalid pos + error("error: invalid key to 'next'") + end + end + -- () used to shrink the return stack to one value + return it.fieldno, (msgpackffi.decode_unchecked(field)) +end; + +-- precreated iterator for tuple_next +local next_it = ffi.new(tuple_iterator_t) + +-- See http://www.lua.org/manual/5.2/manual.html#pdf-next +local function tuple_next(tuple, pos) + return tuple_iterator_next(next_it, tuple, pos); +end + +-- See http://www.lua.org/manual/5.2/manual.html#pdf-ipairs +local function tuple_ipairs(tuple, pos) + local it = ffi.new(tuple_iterator_t) + return it, tuple, pos +end + -- cfuncs table is set by C part + local methods = { - ["next"] = cfuncs.next; - ["pairs"] = cfuncs.pairs; + ["next"] = tuple_next; + ["ipairs"] = tuple_ipairs; + ["pairs"] = tuple_ipairs; -- just alias for ipairs() ["slice"] = cfuncs.slice; ["transform"] = cfuncs.transform; ["find"] = cfuncs.find; @@ -38,7 +100,7 @@ local methods = { } local tuple_gc = cfuncs.__gc; - +local const_struct_tuple_ref_t = ffi.typeof('const struct tuple&') local tuple_field = function(tuple, field_n) local field = builtin.tuple_field(tuple, field_n) if field == nil then @@ -63,7 +125,18 @@ ffi.metatype('struct tuple', { return tuple_field(tuple, key) end return methods[key] - end + end; + __eq = function(tuple_a, tuple_b) + -- Two tuple are considered equal if they have same memory address + return ffi.cast('void *', tuple_a) == ffi.cast('void *', tuple_b); + end; + __pairs = tuple_ipairs; -- Lua 5.2 compatibility + __ipairs = tuple_ipairs; -- Lua 5.2 compatibility +}) + +ffi.metatype(tuple_iterator_t, { + __call = tuple_iterator_next; + __tostring = function(it) return "<tuple iterator>" end; }) -- Remove the global variable diff --git a/src/box/tuple.h b/src/box/tuple.h index 58a0a649311fdf7a516e8231ba3cb999b7a49385..0ea1555e5ed1b3c4ba61b632ddc3a15eea0fc7f0 100644 --- a/src/box/tuple.h +++ b/src/box/tuple.h @@ -324,7 +324,7 @@ struct tuple_iterator { * @param[out] it tuple iterator * @param[in] tuple tuple */ -static inline void +extern "C" inline void tuple_rewind(struct tuple_iterator *it, const struct tuple *tuple) { it->tuple = tuple; @@ -339,7 +339,7 @@ tuple_rewind(struct tuple_iterator *it, const struct tuple *tuple) * @retval field if the iterator has the requested field * @retval NULL otherwise (iteration is out of range) */ -const char * +extern "C" const char * tuple_seek(struct tuple_iterator *it, uint32_t field_no); /** @@ -347,7 +347,7 @@ tuple_seek(struct tuple_iterator *it, uint32_t field_no); * @param it tuple iterator * @return next field or NULL if the iteration is out of range */ -const char * +extern "C" const char * tuple_next(struct tuple_iterator *it); /** diff --git a/src/ffisyms.cc b/src/ffisyms.cc index 523b9a9284732573033ba81a1cfadc0498700712..887bda605dad97a05c0b67221741036019a6d60a 100644 --- a/src/ffisyms.cc +++ b/src/ffisyms.cc @@ -12,5 +12,8 @@ void *ffi_symbols[] = { (void *) mp_bswap_float, (void *) mp_bswap_double, (void *) tuple_arity, - (void *) tuple_field + (void *) tuple_field, + (void *) tuple_rewind, + (void *) tuple_seek, + (void *) tuple_next }; diff --git a/test/box/tuple.result b/test/box/tuple.result index c7bfdd862d45994b569c054ea10fb54ccff743a0..50713b7c5e8e996a81bea04002a1e8c3d6b9dee0 100644 --- a/test/box/tuple.result +++ b/test/box/tuple.result @@ -313,28 +313,108 @@ t[nil] --- - null ... -# test tuple iterators +-------------------------------------------------------------------------------- +-- test tuple:next +-------------------------------------------------------------------------------- +t = box.tuple.new({'a', 'b', 'c'}) --- ... +state, val = t:next() +--- +... +state, val +--- +- 1 +- a +... +state, val = t:next(state) +--- +... +state, val +--- +- 2 +- b +... +state, val = t:next(state) +--- +... +state, val +--- +- 3 +- c +... +state, val = t:next(state) +--- +... +state, val +--- +- null +- null +... +t:next(nil) +--- +- 1 +- a +... +t:next(0) +--- +- 1 +- a +... +t:next(1) +--- +- 2 +- b +... +t:next(2) +--- +- 3 +- c +... +t:next(3) +--- +- null +... +t:next(4) +--- +- error: '[string "-- tuple.lua (internal file)..."]:64: error: invalid key to ''next''' +... +t:next(-1) +--- +- error: '[string "-- tuple.lua (internal file)..."]:64: error: invalid key to ''next''' +... +t:next("fdsaf") +--- +- error: '[string "-- tuple.lua (internal file)..."]:46: error: invalid key to ''next''' +... +box.tuple.new({'x', 'y', 'z'}):next() +--- +- 1 +- x +... t=space:insert{1953719668} --- ... t:next(1684234849) --- -- error: 'tuple.next(): bad arguments' +- error: '[string "-- tuple.lua (internal file)..."]:64: error: invalid key to ''next''' ... t:next(1) --- -- error: 'tuple.next(): bad arguments' +- null ... -t:next(t) +t:next(nil) --- -- error: 'tuple.next(): bad arguments' +- 1 +- 1953719668 ... t:next(t:next()) --- -- error: 'tuple.next(): bad arguments' +- null ... +-------------------------------------------------------------------------------- +-- test tuple:pairs +-------------------------------------------------------------------------------- ta = {} for k, v in t:pairs() do table.insert(ta, v) end --- ... @@ -378,12 +458,129 @@ ta - c - d ... -it, field = t:next() +t = box.tuple.new({'a', 'b', 'c'}) +--- +... +gen, init, state = t:pairs() +--- +... +gen, init, state +--- +- <tuple iterator> +- ['a', 'b', 'c'] +- null +... +state, val = gen(init, state) +--- +... +state, val +--- +- 1 +- a +... +state, val = gen(init, state) +--- +... +state, val +--- +- 2 +- b +... +state, val = gen(init, state) +--- +... +state, val +--- +- 3 +- c +... +state, val = gen(init, state) +--- +... +state, val +--- +- null +- null +... +r = {} +--- +... +for _state, val in t:pairs() do table.insert(r, val) end +--- +... +r +--- +- - a + - b + - c +... +r = {} +--- +... +for _state, val in t:pairs() do table.insert(r, val) end +--- +... +r +--- +- - a + - b + - c +... +r = {} --- ... -getmetatable(it) +for _state, val in t:pairs(1) do table.insert(r, val) end +--- +... +r +--- +- - b + - c +... +r = {} +--- +... +for _state, val in t:pairs(3) do table.insert(r, val) end +--- +... +r +--- +- [] +... +r = {} +--- +... +for _state, val in t:pairs(10) do table.insert(r, val) end +--- +- error: '[string "-- tuple.lua (internal file)..."]:64: error: invalid key to ''next''' +... +r +--- +- [] +... +r = {} +--- +... +for _state, val in t:pairs(nil) do table.insert(r, val) end +--- +... +r +--- +- - a + - b + - c +... +t:pairs(nil) +--- +- <tuple iterator> +- ['a', 'b', 'c'] +- null +... +t:pairs("fdsaf") --- -- box.tuple.iterator +- <tuple iterator> +- ['a', 'b', 'c'] +- fdsaf ... space:drop() --- diff --git a/test/box/tuple.test.lua b/test/box/tuple.test.lua index fd7580b4e5747f7a20b00f9aba25884c71d36d2f..e2d0912067177bf31afcbe685993815dbbcb339e 100644 --- a/test/box/tuple.test.lua +++ b/test/box/tuple.test.lua @@ -94,12 +94,41 @@ space:truncate() -- A test case for Bug#1119389 '(lbox_tuple_index) crashes on 'nil' argument' t=space:insert{0, 8989} t[nil] -# test tuple iterators + +-------------------------------------------------------------------------------- +-- test tuple:next +-------------------------------------------------------------------------------- + +t = box.tuple.new({'a', 'b', 'c'}) +state, val = t:next() +state, val +state, val = t:next(state) +state, val +state, val = t:next(state) +state, val +state, val = t:next(state) +state, val +t:next(nil) +t:next(0) +t:next(1) +t:next(2) +t:next(3) +t:next(4) +t:next(-1) +t:next("fdsaf") + +box.tuple.new({'x', 'y', 'z'}):next() + t=space:insert{1953719668} t:next(1684234849) t:next(1) -t:next(t) +t:next(nil) t:next(t:next()) + +-------------------------------------------------------------------------------- +-- test tuple:pairs +-------------------------------------------------------------------------------- + ta = {} for k, v in t:pairs() do table.insert(ta, v) end ta t=space:replace{1953719668, 'another field'} @@ -111,6 +140,44 @@ ta t=box.tuple.new({'a', 'b', 'c', 'd'}) ta = {} for it,field in t:pairs() do table.insert(ta, field); end ta -it, field = t:next() -getmetatable(it) + +t = box.tuple.new({'a', 'b', 'c'}) +gen, init, state = t:pairs() +gen, init, state +state, val = gen(init, state) +state, val +state, val = gen(init, state) +state, val +state, val = gen(init, state) +state, val +state, val = gen(init, state) +state, val + +r = {} +for _state, val in t:pairs() do table.insert(r, val) end +r + +r = {} +for _state, val in t:pairs() do table.insert(r, val) end +r + +r = {} +for _state, val in t:pairs(1) do table.insert(r, val) end +r + +r = {} +for _state, val in t:pairs(3) do table.insert(r, val) end +r + +r = {} +for _state, val in t:pairs(10) do table.insert(r, val) end +r + +r = {} +for _state, val in t:pairs(nil) do table.insert(r, val) end +r + +t:pairs(nil) +t:pairs("fdsaf") + space:drop()