From c452cb5b4a9c2b9c52e882557bb8c16a8476f512 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov <kostja.osipov@gmail.com> Date: Thu, 13 Oct 2011 01:08:58 +0400 Subject: [PATCH] Lua: implement tuple iterators and methods. Add tuple::pairs() and tuple:next() implementation. Update tuple metatable __index implementation to do method lookup. Fix a bug in box.unpack() where we wouldn't check that lua_tolstring() can return NULL (which happens if nil is on the stack). Add tests. --- core/tarantool_lua.m | 6 ++- mod/box/box_lua.m | 112 ++++++++++++++++++++++++++++++++++--------- test/box/lua.result | 43 +++++++++++++++++ test/box/lua.test | 12 +++++ 4 files changed, 149 insertions(+), 24 deletions(-) diff --git a/core/tarantool_lua.m b/core/tarantool_lua.m index db75c078bd..ac998e3807 100644 --- a/core/tarantool_lua.m +++ b/core/tarantool_lua.m @@ -165,6 +165,7 @@ lbox_unpack(struct lua_State *L) int i = 2; /* first arg comes second */ int nargs = lua_gettop(L); size_t size; + const char *str; u32 u32buf; while (*format) { @@ -172,9 +173,10 @@ lbox_unpack(struct lua_State *L) luaL_error(L, "box.unpack: argument count does not match the format"); switch (*format) { case 'i': - u32buf = * (u32 *) lua_tolstring(L, i, &size); - if (size != sizeof(u32)) + str = lua_tolstring(L, i, &size); + if (str == NULL || size != sizeof(u32)) luaL_error(L, "box.unpack('%c'): got %d bytes (expected: 4)", *format, (int) size); + u32buf = * (u32 *) str; lua_pushnumber(L, u32buf); break; default: diff --git a/mod/box/box_lua.m b/mod/box/box_lua.m index 78f8d9f6bb..86cbb86613 100644 --- a/mod/box/box_lua.m +++ b/mod/box/box_lua.m @@ -104,17 +104,31 @@ lbox_tuple_len(struct lua_State *L) return 1; } +/** + * Implementation of tuple __index metamethod. + * + * Provides operator [] access to individual fields for integer + * indexes, as well as searches and invokes metatable methods + * for strings. + */ static int lbox_tuple_index(struct lua_State *L) { struct box_tuple *tuple = lua_checktuple(L, 1); - int i = luaL_checkint(L, 2); - if (i >= tuple->cardinality) - luaL_error(L, "%s: index %d is out of bounds (0..%d)", - tuplelib_name, i, tuple->cardinality-1); - void *field = tuple_field(tuple, i); - u32 len = load_varint32(&field); - lua_pushlstring(L, field, len); + /* For integer indexes, implement [] operator */ + if (lua_isnumber(L, 2)) { + int i = luaL_checkint(L, 2); + if (i >= tuple->cardinality) + luaL_error(L, "%s: index %d is out of bounds (0..%d)", + tuplelib_name, i, tuple->cardinality-1); + void *field = tuple_field(tuple, i); + u32 len = load_varint32(&field); + lua_pushlstring(L, field, len); + return 1; + } + /* If we got a string, try to find a method for it. */ + lua_getmetatable(L, 1); + lua_getfield(L, -1, lua_tostring(L, 2)); return 1; } @@ -143,18 +157,62 @@ lbox_pushtuple(struct lua_State *L, struct box_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 box_tuple *tuple = lua_checktuple(L, 1); + int argc = lua_gettop(L) - 1; + u8 *field; + size_t len; + + if (argc == 0 || (argc == 1 && lua_type(L, 2) == LUA_TNIL)) + field = tuple->data; + else if (argc == 1 && lua_islightuserdata(L, 2)) + field = lua_touserdata(L, 2); + else + luaL_error(L, "tuple.next(): bad arguments"); + + assert(field >= tuple->data); + if (field < tuple->data + tuple->bsize) { + len = load_varint32((void **) &field); + lua_pushlightuserdata(L, field + len); + lua_pushlstring(L, (char *) field, len); + return 2; + } + lua_pushnil(L); + return 1; +} + +/** 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}, {"__len", lbox_tuple_len}, {"__index", lbox_tuple_index}, {"__tostring", lbox_tuple_tostring}, + {"next", lbox_tuple_next}, + {"pairs", lbox_tuple_pairs}, {NULL, NULL} }; /* }}} */ -/** - * {{{ Lua box.index library: access to spaces and indexes +/** {{{ box.index Lua library: access to spaces and indexes */ static const char *indexlib_name = "box.index"; @@ -573,27 +631,37 @@ void box_lua_call(struct box_txn *txn __attribute__((unused)), } } +/** A helper to register a single type metatable. */ +static void +lua_register_type(struct lua_State *L, const char *type_name, + const struct luaL_reg *methods) +{ + luaL_newmetatable(L, type_name); + /* + * Conventionally, make the metatable point to itself + * in __index. If 'methods' contain a field for __index, + * this is a no-op. + */ + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + lua_pushstring(L, type_name); + lua_setfield(L, -2, "__metatable"); + luaL_register(L, NULL, methods); + lua_pop(L, 1); +} + struct lua_State * mod_lua_init(struct lua_State *L) { lua_atpanic(L, box_lua_panic); /* box, box.tuple */ + lua_register_type(L, tuplelib_name, lbox_tuple_meta); luaL_register(L, "box", boxlib); - luaL_newmetatable(L, tuplelib_name); - lua_pushstring(L, tuplelib_name); - lua_setfield(L, -2, "__metatable"); - luaL_register(L, NULL, lbox_tuple_meta); - lua_pop(L, 2); + lua_pop(L, 1); /* box.index */ - luaL_newmetatable(L, indexlib_name); - /* Make the metatable point to itself in __index. */ - lua_pushvalue(L, -1); - lua_setfield(L, -2, "__index"); - lua_pushstring(L, indexlib_name); - lua_setfield(L, -2, "__metatable"); - luaL_register(L, NULL, lbox_index_meta); + lua_register_type(L, indexlib_name, lbox_index_meta); luaL_register(L, "box.index", indexlib); - lua_pop(L, 2); + lua_pop(L, 1); /* Load box.lua */ if (luaL_dostring(L, &_binary_box_lua_start)) panic("Error loading box.lua: %s", lua_tostring(L, -1)); diff --git a/test/box/lua.result b/test/box/lua.result index cc822e853a..71dd95f4f0 100644 --- a/test/box/lua.result +++ b/test/box/lua.result @@ -477,3 +477,46 @@ lua box.space[0]:delete('test') --- - 1953719668: {'bye, world'} ... +lua t=box.space[0]:insert('test') +--- +... +lua t:next('abcd') +--- +error: 'Lua error: tuple.next(): bad arguments' +... +lua t:next(1) +--- +error: 'Lua error: tuple.next(): bad arguments' +... +lua t:next(t) +--- +error: 'Lua error: tuple.next(): bad arguments' +... +lua t:next(t:next()) +--- +error: 'Lua error: tuple.next(): bad arguments' +... +lua for k, v in t:pairs() do print(v) end +--- +test +... +lua t=box.space[0]:replace('test', 'another field') +--- +... +lua for k, v in t:pairs() do print(v) end +--- +test +another field +... +lua t=box.space[0]:replace('test', 'another field', 'one more') +--- +... +lua for k, v in t:pairs() do print(v) end +--- +test +another field +one more +... +lua box.space[0]:truncate() +--- +... diff --git a/test/box/lua.test b/test/box/lua.test index c457b4b1e6..8fcd3fc58f 100644 --- a/test/box/lua.test +++ b/test/box/lua.test @@ -130,3 +130,15 @@ exec admin "lua #box.index.new(0,0)" exec admin "lua box.space[0]:insert('test', 'hello world')" exec admin "lua box.space[0]:update('test', '=p', 1, 'bye, world')" exec admin "lua box.space[0]:delete('test')" +# test tuple iterators +exec admin "lua t=box.space[0]:insert('test')" +exec admin "lua t:next('abcd')" +exec admin "lua t:next(1)" +exec admin "lua t:next(t)" +exec admin "lua t:next(t:next())" +exec admin "lua for k, v in t:pairs() do print(v) end" +exec admin "lua t=box.space[0]:replace('test', 'another field')" +exec admin "lua for k, v in t:pairs() do print(v) end" +exec admin "lua t=box.space[0]:replace('test', 'another field', 'one more')" +exec admin "lua for k, v in t:pairs() do print(v) end" +exec admin "lua box.space[0]:truncate()" -- GitLab