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