diff --git a/src/box/lua/call.cc b/src/box/lua/call.cc
index 3fce6f259af60611cc6d409121668ef1c15714e7..f047bead61a492f18fe86244d077be55c710cfcb 100644
--- a/src/box/lua/call.cc
+++ b/src/box/lua/call.cc
@@ -84,6 +84,7 @@ struct port_lua
 {
 	struct port_vtab *vtab;
 	struct lua_State *L;
+	size_t size; /* for port_lua_add_tuple */
 };
 
 static inline struct port_lua *
@@ -123,30 +124,26 @@ port_lua_table_add_tuple(struct port *port, struct tuple *tuple)
 {
 	lua_State *L = port_lua(port)->L;
 	try {
-		int idx = luaL_getn(L, -1);	/* TODO: can be optimized */
 		lbox_pushtuple(L, tuple);
-		lua_rawseti(L, -2, idx + 1);
-
+		lua_rawseti(L, -2, ++port_lua(port)->size);
 	} catch (...) {
 		tnt_raise(ClientError, ER_PROC_LUA, lua_tostring(L, -1));
 	}
 }
 
 /** Add all tuples to a Lua table. */
-static struct port *
-port_lua_table_create(struct lua_State *L)
+void
+port_lua_table_create(struct port_lua *port, struct lua_State *L)
 {
 	static struct port_vtab port_lua_vtab = {
 		port_lua_table_add_tuple,
 		null_port_eof,
 	};
-	struct port_lua *port = (struct port_lua *)
-			region_alloc(&fiber()->gc, sizeof(struct port_lua));
 	port->vtab = &port_lua_vtab;
 	port->L = L;
+	port->size = 0;
 	/* The destination table to append tuples to. */
 	lua_newtable(L);
-	return (struct port *) port;
 }
 
 /* }}} */
@@ -180,12 +177,12 @@ lbox_process(lua_State *L)
 		return luaL_error(L, "box.process(CALL, ...) is not allowed");
 	}
 	/* Capture all output into a Lua table. */
-	struct port *port_lua = port_lua_table_create(L);
+	struct port_lua port_lua;
 	struct request request;
 	request_create(&request, op);
 	request_decode(&request, req, sz);
-	box_process(&request, port_lua);
-
+	port_lua_table_create(&port_lua, L);
+	box_process(&request, (struct port *) &port_lua);
 	return 1;
 }
 
@@ -298,6 +295,27 @@ boxffi_select(struct port_ffi *port, uint32_t space_id, uint32_t index_id,
 	}
 }
 
+static int
+lbox_select(lua_State *L)
+{
+	if (lua_gettop(L) != 6 || !lua_isnumber(L, 1) || !lua_isnumber(L, 2) ||
+		!lua_isnumber(L, 3) || !lua_isnumber(L, 4) || !lua_isnumber(L, 5)) {
+		return luaL_error(L, "Usage index:select(space_id, index_id,"
+			"iterator, offset, limit, key)");
+	}
+
+	struct request request;
+	struct port_lua port;
+	lbox_request_create(&request, L, IPROTO_SELECT, 6, -1);
+	request.index_id = lua_tointeger(L, 2);
+	request.iterator = lua_tointeger(L, 3);
+	request.offset = lua_tointeger(L, 4);
+	request.limit = lua_tointeger(L, 5);
+	port_lua_table_create(&port, L);
+	box_process(&request, (struct port *) &port);
+	return 1;
+}
+
 static int
 lbox_insert(lua_State *L)
 {
@@ -763,6 +781,7 @@ static const struct luaL_reg boxlib[] = {
 static const struct luaL_reg boxlib_internal[] = {
 	{"process", lbox_process},
 	{"call_loadproc",  lbox_call_loadproc},
+	{"select", lbox_select},
 	{"insert", lbox_insert},
 	{"replace", lbox_replace},
 	{"update", lbox_update},
@@ -779,6 +798,14 @@ box_lua_init(struct lua_State *L)
 	luaL_register(L, "box.internal", boxlib_internal);
 	lua_pop(L, 1);
 
+#if 0
+	/* Get CTypeID for `struct port *' */
+	int rc = luaL_cdef(L, "struct port;");
+	assert(rc == 0);
+	(void) rc;
+	CTID_STRUCT_PORT_PTR = luaL_ctypeid(L, "struct port *");
+	assert(CTID_CONST_STRUCT_TUPLE_REF != 0);
+#endif
 	box_lua_error_init(L);
 	box_lua_tuple_init(L);
 	box_lua_index_init(L);
diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc
index 9e86f19cc96c39e6a86906b794bc90779d36175d..9aa8c01cf2ba250e9f5d259f934e3e4d60a1a1c8 100644
--- a/src/box/lua/error.cc
+++ b/src/box/lua/error.cc
@@ -40,6 +40,14 @@ extern "C" {
 #include "lua/utils.h"
 #include "box/error.h"
 
+int
+lbox_error(lua_State *L)
+{
+	(void) L;
+	diag_last_error(&fiber()->diag)->raise();
+	return 0;
+}
+
 static int
 lbox_error_raise(lua_State *L)
 {
@@ -53,9 +61,8 @@ lbox_error_raise(lua_State *L)
 	int top = lua_gettop(L);
 	if (top <= 1) {
 		/* re-throw saved exceptions (if any) */
-		Exception *e = diag_last_error(&fiber()->diag);
-		if (e != NULL)
-			e->raise();
+		if (diag_last_error(&fiber()->diag))
+			lbox_error(L);
 		return 0;
 	} else if (top >= 2 && lua_type(L, 2) == LUA_TNUMBER) {
 		code = lua_tointeger(L, 2);
diff --git a/src/box/lua/error.h b/src/box/lua/error.h
index f0dc5895f9834700a901211f522b3ec62afbd12c..9661f4e147194576579296e65836d81f03ce6fc2 100644
--- a/src/box/lua/error.h
+++ b/src/box/lua/error.h
@@ -34,4 +34,7 @@ struct lua_State;
 void
 box_lua_error_init(struct lua_State *L);
 
+int
+lbox_error(struct lua_State *L);
+
 #endif /* INCLUDES_TARANTOOL_LUA_ERROR_H */
diff --git a/src/box/lua/index.cc b/src/box/lua/index.cc
index 7555537c554a2e2add560b41f6e2d08e7cd6360f..b81fa2a96314d35d86ca3ff5b1bc5f31b0c15e54 100644
--- a/src/box/lua/index.cc
+++ b/src/box/lua/index.cc
@@ -28,17 +28,22 @@
  */
 #include "box/lua/index.h"
 #include "lua/utils.h"
+#include "lua/msgpack.h"
 #include "box/index.h"
 #include "box/space.h"
 #include "box/schema.h"
 #include "box/user_def.h"
 #include "box/tuple.h"
+#include "box/lua/error.h"
 #include "box/lua/tuple.h"
 #include "fiber.h"
+#include "iobuf.h"
 
 /** {{{ box.index Lua library: access to spaces and indexes
  */
 
+static int CTID_STRUCT_ITERATOR_REF = 0;
+
 static inline Index *
 check_index(uint32_t space_id, uint32_t index_id)
 {
@@ -67,21 +72,69 @@ boxffi_index_len(uint32_t space_id, uint32_t index_id)
 	}
 }
 
+static inline int
+lbox_returntuple(lua_State *L, struct tuple *tuple)
+{
+	if (tuple == (struct tuple *) -1) {
+		return lbox_error(L);
+	} else if (tuple == NULL) {
+		lua_pushnil(L);
+		return 1;
+	} else {
+		lbox_pushtuple_noref(L, tuple);
+		return 1;
+	}
+}
+
+static inline struct tuple *
+boxffi_returntuple(struct tuple *tuple)
+{
+	if (tuple == NULL)
+		return NULL;
+	tuple_ref(tuple);
+	return tuple;
+}
+
+static inline char *
+lbox_tokey(lua_State *L, int idx)
+{
+	struct region *gc = &fiber()->gc;
+	size_t used = region_used(gc);
+	struct mpstream stream;
+	mpstream_init(&stream, gc, region_reserve_cb, region_alloc_cb);
+	luamp_encode_tuple(L, luaL_msgpack_default, &stream, idx);
+	mpstream_flush(&stream);
+	size_t key_len = region_used(gc) - used;
+	return (char *) region_join(gc, key_len);
+}
+
 struct tuple *
 boxffi_index_random(uint32_t space_id, uint32_t index_id, uint32_t rnd)
 {
 	try {
 		Index *index = check_index(space_id, index_id);
 		struct tuple *tuple = index->random(rnd);
-		if (tuple == NULL)
-			return NULL;
-		tuple_ref(tuple); /* must not throw in this case */
-		return tuple;
+		return boxffi_returntuple(tuple);
 	}  catch (Exception *) {
 		return (struct tuple *) -1; /* handled by box.error() in Lua */
 	}
 }
 
+static int
+lbox_index_random(lua_State *L)
+{
+	if (lua_gettop(L) != 3 || !lua_isnumber(L, 1) || !lua_isnumber(L, 2) ||
+	    !lua_isnumber(L, 3))
+		return luaL_error(L, "Usage index.random(space_id, index_id, rnd)");
+
+	uint32_t space_id = lua_tointeger(L, 1);
+	uint32_t index_id = lua_tointeger(L, 2);
+	uint32_t rnd = lua_tointeger(L, 3);
+
+	struct tuple *tuple = boxffi_index_random(space_id, index_id, rnd);
+	return lbox_returntuple(L, tuple);
+}
+
 struct tuple *
 boxffi_index_get(uint32_t space_id, uint32_t index_id, const char *key)
 {
@@ -92,15 +145,27 @@ boxffi_index_get(uint32_t space_id, uint32_t index_id, const char *key)
 		uint32_t part_count = key ? mp_decode_array(&key) : 0;
 		primary_key_validate(index->key_def, key, part_count);
 		struct tuple *tuple = index->findByKey(key, part_count);
-		if (tuple == NULL)
-			return NULL;
-		tuple_ref(tuple); /* must not throw in this case */
-		return tuple;
+		return boxffi_returntuple(tuple);
 	}  catch (Exception *) {
 		return (struct tuple *) -1; /* handled by box.error() in Lua */
 	}
 }
 
+static int
+lbox_index_get(lua_State *L)
+{
+	if (lua_gettop(L) != 3 || !lua_isnumber(L, 1) || !lua_isnumber(L, 2))
+		return luaL_error(L, "Usage index.get(space_id, index_id, key)");
+
+	RegionGuard region_guard(&fiber()->gc);
+	uint32_t space_id = lua_tointeger(L, 1);
+	uint32_t index_id = lua_tointeger(L, 2);
+	const char *key = lbox_tokey(L, 3);
+
+	struct tuple *tuple = boxffi_index_get(space_id, index_id, key);
+	return lbox_returntuple(L, tuple);
+}
+
 struct tuple *
 boxffi_index_min(uint32_t space_id, uint32_t index_id, const char *key)
 {
@@ -115,15 +180,27 @@ boxffi_index_min(uint32_t space_id, uint32_t index_id, const char *key)
 		uint32_t part_count = key ? mp_decode_array(&key) : 0;
 		key_validate(index->key_def, ITER_GE, key, part_count);
 		struct tuple *tuple = index->min(key, part_count);
-		if (tuple == NULL)
-			return NULL;
-		tuple_ref(tuple); /* must not throw in this case */
-		return tuple;
+		return boxffi_returntuple(tuple);
 	}  catch (Exception *) {
 		return (struct tuple *) -1; /* handled by box.error() in Lua */
 	}
 }
 
+static int
+lbox_index_min(lua_State *L)
+{
+	if (lua_gettop(L) != 3 || !lua_isnumber(L, 1) || !lua_isnumber(L, 2))
+		return luaL_error(L, "usage index.min(space_id, index_id, key)");
+
+	RegionGuard region_guard(&fiber()->gc);
+	uint32_t space_id = lua_tointeger(L, 1);
+	uint32_t index_id = lua_tointeger(L, 2);
+	const char *key = lbox_tokey(L, 3);
+
+	struct tuple *tuple = boxffi_index_min(space_id, index_id, key);
+	return lbox_returntuple(L, tuple);
+}
+
 struct tuple *
 boxffi_index_max(uint32_t space_id, uint32_t index_id, const char *key)
 {
@@ -138,15 +215,27 @@ boxffi_index_max(uint32_t space_id, uint32_t index_id, const char *key)
 		uint32_t part_count = key ? mp_decode_array(&key) : 0;
 		key_validate(index->key_def, ITER_LE, key, part_count);
 		struct tuple *tuple = index->max(key, part_count);
-		if (tuple == NULL)
-			return NULL;
-		tuple_ref(tuple); /* must not throw in this case */
-		return tuple;
+		return boxffi_returntuple(tuple);
 	}  catch (Exception *) {
 		return (struct tuple *) -1; /* handled by box.error() in Lua */
 	}
 }
 
+static int
+lbox_index_max(lua_State *L)
+{
+	if (lua_gettop(L) != 3 || !lua_isnumber(L, 1) || !lua_isnumber(L, 2))
+		return luaL_error(L, "usage index.max(space_id, index_id, key)");
+
+	RegionGuard region_guard(&fiber()->gc);
+	uint32_t space_id = lua_tointeger(L, 1);
+	uint32_t index_id = lua_tointeger(L, 2);
+	const char *key = lbox_tokey(L, 3);
+
+	struct tuple *tuple = boxffi_index_max(space_id, index_id, key);
+	return lbox_returntuple(L, tuple);
+}
+
 ssize_t
 boxffi_index_count(uint32_t space_id, uint32_t index_id, int type, const char *key)
 {
@@ -160,6 +249,28 @@ boxffi_index_count(uint32_t space_id, uint32_t index_id, int type, const char *k
 		return -1; /* handled by box.error() in Lua */
 	}
 }
+static int
+lbox_index_count(lua_State *L)
+{
+	if (lua_gettop(L) != 4 || !lua_isnumber(L, 1) || !lua_isnumber(L, 2) ||
+	    !lua_isnumber(L, 3)) {
+		return luaL_error(L, "usage index.count(space_id, index_id, "
+		       "iterator, key)");
+	}
+
+	RegionGuard region_guard(&fiber()->gc);
+	uint32_t space_id = lua_tointeger(L, 1);
+	uint32_t index_id = lua_tointeger(L, 2);
+	uint32_t iterator = lua_tointeger(L, 3);
+	const char *key = lbox_tokey(L, 4);
+
+	ssize_t count = boxffi_index_count(space_id, index_id,
+		iterator, key);
+	if (count == -1)
+		return lbox_error(L);
+	lua_pushinteger(L, count);
+	return 1;
+}
 
 static void
 box_index_init_iterator_types(struct lua_State *L, int idx)
@@ -197,11 +308,35 @@ boxffi_index_iterator(uint32_t space_id, uint32_t index_id, int type,
 	} catch (Exception *) {
 		if (it)
 			it->free(it);
-		/* will be hanled by box.error() in Lua */
+		/* will be handled by box.error() in Lua */
 		return NULL;
 	}
 }
 
+static int
+lbox_index_iterator(lua_State *L)
+{
+	if (lua_gettop(L) != 4 || !lua_isnumber(L, 1) || !lua_isnumber(L, 2) ||
+	    !lua_isnumber(L, 3))
+		return luaL_error(L, "usage index.iterator(space_id, index_id, type, key)");
+
+	uint32_t space_id = lua_tointeger(L, 1);
+	uint32_t index_id = lua_tointeger(L, 2);
+	uint32_t iterator = lua_tointeger(L, 3);
+	/* const char *key = lbox_tokey(L, 4); */
+	const char *mpkey = lua_tolstring(L, 4, NULL); /* Key encoded by Lua */
+	struct iterator *it = boxffi_index_iterator(space_id, index_id,
+		iterator, mpkey);
+	if (it == NULL)
+		return lbox_error(L);
+
+	assert(CTID_STRUCT_ITERATOR_REF != 0);
+	struct iterator **ptr = (struct iterator **) luaL_pushcdata(L,
+		CTID_STRUCT_ITERATOR_REF, sizeof(struct iterator *));
+	*ptr = it; /* NULL handled by Lua, gc also set by Lua */
+	return 1;
+}
+
 struct tuple*
 boxffi_iterator_next(struct iterator *itr)
 {
@@ -228,11 +363,36 @@ boxffi_iterator_next(struct iterator *itr)
 	}
 }
 
+static int
+lbox_iterator_next(lua_State *L)
+{
+	/* first argument is key buffer */
+	if (lua_gettop(L) < 1 || lua_type(L, 1) != LUA_TCDATA)
+		return luaL_error(L, "usage: next(state)");
+
+	assert(CTID_STRUCT_ITERATOR_REF != 0);
+	uint32_t ctypeid;
+	void *data = luaL_checkcdata(L, 1, &ctypeid);
+	if (ctypeid != CTID_STRUCT_ITERATOR_REF)
+		return luaL_error(L, "usage: next(state)");
+
+	struct iterator *itr = *(struct iterator **) data;
+	struct tuple *tuple = boxffi_iterator_next(itr);
+	return lbox_returntuple(L, tuple);
+}
+
 /* }}} */
 
 void
 box_lua_index_init(struct lua_State *L)
 {
+	/* Get CTypeIDs */
+	int rc = luaL_cdef(L, "struct iterator;");
+	assert(rc == 0);
+	(void) rc;
+	CTID_STRUCT_ITERATOR_REF = luaL_ctypeid(L, "struct iterator&");
+	assert(CTID_STRUCT_ITERATOR_REF != 0);
+
 	static const struct luaL_reg indexlib [] = {
 		{NULL, NULL}
 	};
@@ -241,4 +401,18 @@ box_lua_index_init(struct lua_State *L)
 	luaL_register_module(L, "box.index", indexlib);
 	box_index_init_iterator_types(L, -2);
 	lua_pop(L, 1);
+
+	static const struct luaL_reg boxlib_internal[] = {
+		{"random", lbox_index_random},
+		{"get",  lbox_index_get},
+		{"min", lbox_index_min},
+		{"max", lbox_index_max},
+		{"count", lbox_index_count},
+		{"iterator", lbox_index_iterator},
+		{"iterator_next", lbox_iterator_next},
+		{NULL, NULL}
+	};
+
+	luaL_register(L, "box.internal", boxlib_internal);
+	lua_pop(L, 1);
 }
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index d5eb948d49eed7b6e3b970878d9940b78ecaaeb5..a02d49643c65b9eea2f4c041f90a0a76589e5ee1 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -1,6 +1,7 @@
 -- schema.lua (internal file)
 --
 local ffi = require('ffi')
+local msgpack = require('msgpack')
 local msgpackffi = require('msgpackffi')
 local fun = require('fun')
 local session = box.session
@@ -524,7 +525,7 @@ local iterator_gen = function(param, state)
         information.
     --]]
     if not ffi.istype(iterator_t, state) then
-        error('usage gen(param, state)')
+        error('usage: next(param, state)')
     end
     -- next() modifies state in-place
     local tuple = builtin.boxffi_iterator_next(state)
@@ -537,6 +538,15 @@ local iterator_gen = function(param, state)
     end
 end
 
+local iterator_gen_luac = function(param, state)
+    local tuple = internal.iterator_next(state)
+    if tuple ~= nil then
+        return state, tuple -- new state, value
+    else
+        return nil
+    end
+end
+
 local iterator_cdata_gc = function(iterator)
     return iterator.free(iterator)
 end
@@ -604,7 +614,7 @@ function box.schema.space.bless(space)
         return error('Attempt to modify a read-only table') end
     index_mt.__index = index_mt
     -- min and max
-    index_mt.min = function(index, key)
+    index_mt.min_ffi = function(index, key)
         local pkey = msgpackffi.encode_tuple(key)
         local tuple = builtin.boxffi_index_min(index.space_id, index.id, pkey)
         if tuple == ffi.cast('void *', -1) then
@@ -615,7 +625,11 @@ function box.schema.space.bless(space)
             return
         end
     end
-    index_mt.max = function(index, key)
+    index_mt.min_luac = function(index, key)
+        key = keify(key)
+        return internal.min(index.space_id, index.id, key);
+    end
+    index_mt.max_ffi = function(index, key)
         local pkey = msgpackffi.encode_tuple(key)
         local tuple = builtin.boxffi_index_max(index.space_id, index.id, pkey)
         if tuple == ffi.cast('void *', -1) then
@@ -626,7 +640,11 @@ function box.schema.space.bless(space)
             return
         end
     end
-    index_mt.random = function(index, rnd)
+    index_mt.max_luac = function(index, key)
+        key = keify(key)
+        return internal.max(index.space_id, index.id, key);
+    end
+    index_mt.random_ffi = function(index, rnd)
         rnd = rnd or math.random()
         local tuple = builtin.boxffi_index_random(index.space_id, index.id, rnd)
         if tuple == ffi.cast('void *', -1) then
@@ -637,8 +655,12 @@ function box.schema.space.bless(space)
             return
         end
     end
+    index_mt.random_luac = function(index, rnd)
+        rnd = rnd or math.random()
+        return internal.random(index.space_id, index.id, rnd);
+    end
     -- iteration
-    index_mt.pairs = function(index, key, opts)
+    index_mt.pairs_ffi = function(index, key, opts)
         local pkey, pkey_end = msgpackffi.encode_tuple(key)
         local itype = check_iterator_type(opts, pkey + 1 >= pkey_end);
 
@@ -648,13 +670,20 @@ function box.schema.space.bless(space)
         if cdata == nil then
             box.error()
         end
-
         return fun.wrap(iterator_gen, keybuf, ffi.gc(cdata, iterator_cdata_gc))
     end
-    index_mt.__pairs = index_mt.pairs -- Lua 5.2 compatibility
-    index_mt.__ipairs = index_mt.pairs -- Lua 5.2 compatibility
+    index_mt.pairs_luac = function(index, key, opts)
+        key = keify(key)
+        local itype = check_iterator_type(opts, #key == 0);
+        local keymp = msgpack.encode(key)
+        local keybuf = ffi.string(keymp, #keymp)
+        local cdata = internal.iterator(index.space_id, index.id, itype, keymp);
+        return fun.wrap(iterator_gen_luac, keybuf,
+            ffi.gc(cdata, iterator_cdata_gc))
+    end
+
     -- index subtree size
-    index_mt.count = function(index, key, opts)
+    index_mt.count_ffi = function(index, key, opts)
         local pkey, pkey_end = msgpackffi.encode_tuple(key)
         local itype = check_iterator_type(opts, pkey + 1 >= pkey_end);
         local count = builtin.boxffi_index_count(index.space_id, index.id,
@@ -664,6 +693,11 @@ function box.schema.space.bless(space)
         end
         return tonumber(count)
     end
+    index_mt.count_luac = function(index, key, opts)
+        key = keify(key)
+        local itype = check_iterator_type(opts, #key == 0);
+        return internal.count(index.space_id, index.id, itype, key);
+    end
 
     local function check_index(space, index_id)
         if space.index[index_id] == nil then
@@ -671,7 +705,7 @@ function box.schema.space.bless(space)
         end
     end
 
-    index_mt.get = function(index, key)
+    index_mt.get_ffi = function(index, key)
         local key, key_end = msgpackffi.encode_tuple(key)
         local tuple = builtin.boxffi_index_get(index.space_id, index.id, key)
         if tuple == ffi.cast('void *', -1) then
@@ -682,14 +716,15 @@ function box.schema.space.bless(space)
             return
         end
     end
+    index_mt.get_luac = function(index, key)
+        key = keify(key)
+        return internal.get(index.space_id, index.id, key)
+    end
 
-    index_mt.select = function(index, key, opts)
+    local function check_select_opts(opts, key_is_nil)
         local offset = 0
         local limit = 4294967295
-
-        local key, key_end = msgpackffi.encode_tuple(key)
-        local iterator = check_iterator_type(opts, key + 1 >= key_end)
-
+        local iterator = check_iterator_type(opts, key_is_nil)
         if opts ~= nil then
             if opts.offset ~= nil then
                 offset = opts.offset
@@ -698,6 +733,12 @@ function box.schema.space.bless(space)
                 limit = opts.limit
             end
         end
+        return iterator, offset, limit
+    end
+
+    index_mt.select_ffi = function(index, key, opts)
+        local key, key_end = msgpackffi.encode_tuple(key)
+        local iterator, offset, limit = check_select_opts(opts, key + 1 >= key_end)
 
         if builtin.boxffi_select(port, index.space_id,
             index.id, iterator, offset, limit, key, key_end) ~=0 then
@@ -711,6 +752,14 @@ function box.schema.space.bless(space)
         end
         return ret
     end
+
+    index_mt.select_luac = function(index, key, opts)
+        local key = keify(key)
+        local iterator, offset, limit = check_select_opts(opts, #key == 0)
+        return internal.select(index.space_id, index.id, iterator,
+            offset, limit, key)
+    end
+
     index_mt.update = function(index, key, ops)
         return internal.update(index.space_id, index.id, keify(key), ops);
     end
@@ -729,6 +778,21 @@ function box.schema.space.bless(space)
         end
         return box.schema.index.alter(index.space_id, index.id, options)
     end
+
+    -- true if reading operations may yield
+    local read_yields = space.engine == 'sophia'
+    local read_ops = {'select', 'get', 'min', 'max', 'count', 'random', 'pairs'}
+    for _, op in ipairs(read_ops) do
+        if read_yields then
+            -- use Lua/C implmenetation
+            index_mt[op] = index_mt[op .. "_luac"]
+        else
+            -- use FFI implementation
+            index_mt[op] = index_mt[op .. "_ffi"]
+        end
+    end
+    index_mt.__pairs = index_mt.pairs -- Lua 5.2 compatibility
+    index_mt.__ipairs = index_mt.pairs -- Lua 5.2 compatibility
     --
     local space_mt = {}
     space_mt.len = function(space)
diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
index e9c22b280bee7756f163e7555d94a017f445797f..6fa40dde687c1a58ec942ebcee86bca2d4a648c2 100644
--- a/src/box/lua/space.cc
+++ b/src/box/lua/space.cc
@@ -54,8 +54,16 @@ lbox_space_on_replace_trigger(struct trigger *trigger, void *event)
 
 	lua_rawgeti(L, LUA_REGISTRYINDEX, (intptr_t) trigger->data);
 
-	lbox_pushtuple(L, stmt->old_tuple);
-	lbox_pushtuple(L, stmt->new_tuple);
+	if (stmt->old_tuple) {
+		lbox_pushtuple(L, stmt->old_tuple);
+	} else {
+		lua_pushnil(L);
+	}
+	if (stmt->new_tuple) {
+		lbox_pushtuple(L, stmt->new_tuple);
+	} else {
+		lua_pushnil(L);
+	}
 	/* @todo: maybe the space object has to be here */
 	lua_pushstring(L, stmt->space->def.name);
 
diff --git a/src/box/lua/tuple.cc b/src/box/lua/tuple.cc
index ca929b280d169a5d47e52b6eedadfbb45d03802e..323cc18673d264dcc53b8ff870e2aedccdd29bad 100644
--- a/src/box/lua/tuple.cc
+++ b/src/box/lua/tuple.cc
@@ -301,19 +301,14 @@ lbox_tuple_transform(struct lua_State *L)
 }
 
 void
-lbox_pushtuple(struct lua_State *L, struct tuple *tuple)
+lbox_pushtuple_noref(struct lua_State *L, struct tuple *tuple)
 {
-	if (tuple) {
-		assert(CTID_CONST_STRUCT_TUPLE_REF != 0);
-		struct tuple **ptr = (struct tuple **) luaL_pushcdata(L,
-			CTID_CONST_STRUCT_TUPLE_REF, sizeof(struct tuple *));
-		*ptr = tuple;
-		lua_pushcfunction(L, lbox_tuple_gc);
-		luaL_setcdatagc(L, -2);
-		tuple_ref(tuple);
-	} else {
-		return lua_pushnil(L);
-	}
+	assert(CTID_CONST_STRUCT_TUPLE_REF != 0);
+	struct tuple **ptr = (struct tuple **) luaL_pushcdata(L,
+		CTID_CONST_STRUCT_TUPLE_REF, sizeof(struct tuple *));
+	*ptr = tuple;
+	lua_pushcfunction(L, lbox_tuple_gc);
+	luaL_setcdatagc(L, -2);
 }
 
 static const struct luaL_reg lbox_tuple_meta[] = {
diff --git a/src/box/lua/tuple.h b/src/box/lua/tuple.h
index 6d69a831553c1434b72c6a647503b15a848297cd..960278d12085d535d061ac78117d8802713664e7 100644
--- a/src/box/lua/tuple.h
+++ b/src/box/lua/tuple.h
@@ -28,6 +28,9 @@
  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
+
+#include <box/tuple.h>
+
 struct lua_State;
 struct txn;
 struct tuple;
@@ -36,7 +39,15 @@ struct tuple;
  * Push tuple on lua stack
  */
 void
-lbox_pushtuple(struct lua_State *L, struct tuple *tuple);
+lbox_pushtuple_noref(struct lua_State *L, struct tuple *tuple);
+
+static inline void
+lbox_pushtuple(struct lua_State *L, struct tuple *tuple)
+{
+	assert(tuple != NULL);
+	lbox_pushtuple_noref(L, tuple);
+	tuple_ref(tuple);
+}
 
 struct tuple *lua_istuple(struct lua_State *L, int narg);
 
diff --git a/src/lua/utils.cc b/src/lua/utils.cc
index fa62f3901be48d6fe14501b760ed2dfef73863e0..93cf32ce9845cc9b52c59851d96442c5d9950847 100644
--- a/src/lua/utils.cc
+++ b/src/lua/utils.cc
@@ -96,6 +96,23 @@ luaL_ctypeid(struct lua_State *L, const char *ctypename)
 	return ctypeid;
 }
 
+int
+luaL_cdef(struct lua_State *L, const char *what)
+{
+	int idx = lua_gettop(L);
+	/* This function calls ffi.cdef  */
+
+	/* Get ffi.typeof function */
+	luaL_loadstring(L, "return require('ffi').cdef");
+	lua_call(L, 0, 1);
+	/* FFI must exist */
+	assert(lua_gettop(L) == idx + 1 && lua_isfunction(L, idx + 1));
+	/* Push the argument to ffi.cdef */
+	lua_pushstring(L, what);
+	/* Call ffi.cdef() */
+	return lua_pcall(L, 1, 0, 0);
+}
+
 int
 luaL_setcdatagc(struct lua_State *L, int idx)
 {
diff --git a/src/lua/utils.h b/src/lua/utils.h
index f1d346695eb1e4f2e3ae449f4cb755ae8bee1bda..3e5cc54eadd966704035a4ab0047826da772faf7 100644
--- a/src/lua/utils.h
+++ b/src/lua/utils.h
@@ -101,6 +101,17 @@ luaL_setcdatagc(struct lua_State *L, int idx);
 LUA_API uint32_t
 luaL_ctypeid(struct lua_State *L, const char *ctypename);
 
+/**
+* @brief Declare symbols for FFI
+* @param L Lua State
+* @param what C definitions
+* @sa ffi.cdef(def)
+* @retval 0 on success
+* @retval LUA_ERRRUN, LUA_ERRMEM, LUA_ERRERR otherwise
+*/
+LUA_API int
+luaL_cdef(struct lua_State *L, const char *ctypename);
+
 /** \endcond public */
 
 static inline lua_Integer
diff --git a/test/box/select.result b/test/box/select.result
index cc4522a46fd83340722bd7bcee010c85d4c4c0b4..e621bcfb0ce102b73036a8fb5fc03c8ea9617b7b 100644
--- a/test/box/select.result
+++ b/test/box/select.result
@@ -1,3 +1,6 @@
+msgpack = require('msgpack')
+---
+...
 s = box.schema.space.create('select', { temporary = true })
 ---
 ...
@@ -8,65 +11,104 @@ index2 = s:create_index('second', { type = 'tree', unique = true,  parts = {2, '
 ---
 ...
 for i = 1, 20 do s:insert({ i, 1, 2, 3 }) end
+---
+...
+--# setopt delimiter ';'
+local function test_op(op, idx, ...)
+    local t1 = idx[op .. '_ffi'](idx, ...)
+    local t2 = idx[op .. '_luac'](idx, ...)
+    if msgpack.encode(t1) ~= msgpack.encode(t2) then
+        return 'different result from '..op..'_ffi and '..op..'_luac', t1, t2
+    end
+    return t1
+end
+test = setmetatable({}, {
+    __index = function(_, op) return function(...) return test_op(op, ...) end end
+})
+---
+...
+--# setopt delimiter ''
+local function test_op(op, idx, ...)
+    local t1 = idx[op .. '_ffi'](idx, ...)
+    local t2 = idx[op .. '_luac'](idx, ...)
+    if msgpack.encode(t1) ~= msgpack.encode(t2) then
+        return 'different result from '..op..'_ffi and '..op..'_luac', t1, t2
+    end
+    return t1
+end
+test = setmetatable({}, {
+    __index = function(_, op) return function(...) return test_op(op, ...) end end
+})
+
 ---
 ...
 --------------------------------------------------------------------------------
 -- get tests
 --------------------------------------------------------------------------------
-s.index[0]:get()
+s.index[0].get == s.index[0].get_ffi or s.index[0].get == s.index[0].get_luac
+---
+- true
+...
+test.get(s.index[0])
 ---
 - error: Invalid key part count in an exact match (expected 1, got 0)
 ...
-s.index[0]:get({})
+test.get(s.index[0], {})
 ---
 - error: Invalid key part count in an exact match (expected 1, got 0)
 ...
-s.index[0]:get(nil)
+test.get(s.index[0], nil)
 ---
 - error: Invalid key part count in an exact match (expected 1, got 0)
 ...
-s.index[0]:get(1)
+test.get(s.index[0], 1)
 ---
 - [1, 1, 2, 3]
 ...
-s.index[0]:get({1})
+test.get(s.index[0], {1})
 ---
 - [1, 1, 2, 3]
 ...
-s.index[0]:get({1, 2})
+test.get(s.index[0], {1, 2})
 ---
 - error: Invalid key part count in an exact match (expected 1, got 2)
 ...
-s.index[0]:get(0)
+test.get(s.index[0], 0)
 ---
+- null
 ...
-s.index[0]:get({0})
+test.get(s.index[0], {0})
 ---
+- null
 ...
-s.index[0]:get("0")
+test.get(s.index[0], "0")
 ---
 - error: 'Supplied key type of part 0 does not match index part type: expected NUM'
 ...
-s.index[0]:get({"0"})
+test.get(s.index[0], {"0"})
 ---
 - error: 'Supplied key type of part 0 does not match index part type: expected NUM'
 ...
-s.index[1]:get(1)
+test.get(s.index[1], 1)
 ---
 - error: Invalid key part count in an exact match (expected 2, got 1)
 ...
-s.index[1]:get({1})
+test.get(s.index[1], {1})
 ---
 - error: Invalid key part count in an exact match (expected 2, got 1)
 ...
-s.index[1]:get({1, 2})
+test.get(s.index[1], {1, 2})
 ---
 - [2, 1, 2, 3]
 ...
 --------------------------------------------------------------------------------
 -- select tests
 --------------------------------------------------------------------------------
-s.index[0]:select()
+s.index[0].select == s.index[0].select_ffi or s.index[0].select == s.index[0].select_luac
+---
+- true
+...
+test.select(s.index[0])
 ---
 - - [1, 1, 2, 3]
   - [2, 1, 2, 3]
@@ -89,7 +131,7 @@ s.index[0]:select()
   - [19, 1, 2, 3]
   - [20, 1, 2, 3]
 ...
-s.index[0]:select({})
+test.select(s.index[0], {})
 ---
 - - [1, 1, 2, 3]
   - [2, 1, 2, 3]
@@ -112,7 +154,7 @@ s.index[0]:select({})
   - [19, 1, 2, 3]
   - [20, 1, 2, 3]
 ...
-s.index[0]:select(nil)
+test.select(s.index[0], nil)
 ---
 - - [1, 1, 2, 3]
   - [2, 1, 2, 3]
@@ -135,7 +177,7 @@ s.index[0]:select(nil)
   - [19, 1, 2, 3]
   - [20, 1, 2, 3]
 ...
-s.index[0]:select({}, {iterator = 'ALL'})
+test.select(s.index[0], {}, {iterator = 'ALL'})
 ---
 - - [1, 1, 2, 3]
   - [2, 1, 2, 3]
@@ -158,7 +200,7 @@ s.index[0]:select({}, {iterator = 'ALL'})
   - [19, 1, 2, 3]
   - [20, 1, 2, 3]
 ...
-s.index[0]:select(nil, {iterator = box.index.ALL })
+test.select(s.index[0], nil, {iterator = box.index.ALL })
 ---
 - - [1, 1, 2, 3]
   - [2, 1, 2, 3]
@@ -181,7 +223,7 @@ s.index[0]:select(nil, {iterator = box.index.ALL })
   - [19, 1, 2, 3]
   - [20, 1, 2, 3]
 ...
-s.index[0]:select({}, {iterator = box.index.ALL, limit = 10})
+test.select(s.index[0], {}, {iterator = box.index.ALL, limit = 10})
 ---
 - - [1, 1, 2, 3]
   - [2, 1, 2, 3]
@@ -194,15 +236,15 @@ s.index[0]:select({}, {iterator = box.index.ALL, limit = 10})
   - [9, 1, 2, 3]
   - [10, 1, 2, 3]
 ...
-s.index[0]:select(nil, {iterator = box.index.ALL, limit = 0})
+test.select(s.index[0], nil, {iterator = box.index.ALL, limit = 0})
 ---
 - []
 ...
-s.index[0]:select({}, {iterator = 'ALL', limit = 1, offset = 15})
+test.select(s.index[0], {}, {iterator = 'ALL', limit = 1, offset = 15})
 ---
 - - [16, 1, 2, 3]
 ...
-s.index[0]:select(nil, {iterator = 'ALL', limit = 20, offset = 15})
+test.select(s.index[0], nil, {iterator = 'ALL', limit = 20, offset = 15})
 ---
 - - [16, 1, 2, 3]
   - [17, 1, 2, 3]
@@ -210,7 +252,7 @@ s.index[0]:select(nil, {iterator = 'ALL', limit = 20, offset = 15})
   - [19, 1, 2, 3]
   - [20, 1, 2, 3]
 ...
-s.index[0]:select(nil, {iterator = box.index.EQ})
+test.select(s.index[0], nil, {iterator = box.index.EQ})
 ---
 - - [1, 1, 2, 3]
   - [2, 1, 2, 3]
@@ -233,7 +275,7 @@ s.index[0]:select(nil, {iterator = box.index.EQ})
   - [19, 1, 2, 3]
   - [20, 1, 2, 3]
 ...
-s.index[0]:select({}, {iterator = 'EQ'})
+test.select(s.index[0], {}, {iterator = 'EQ'})
 ---
 - - [1, 1, 2, 3]
   - [2, 1, 2, 3]
@@ -256,7 +298,7 @@ s.index[0]:select({}, {iterator = 'EQ'})
   - [19, 1, 2, 3]
   - [20, 1, 2, 3]
 ...
-s.index[0]:select(nil, {iterator = 'REQ'})
+test.select(s.index[0], nil, {iterator = 'REQ'})
 ---
 - - [20, 1, 2, 3]
   - [19, 1, 2, 3]
@@ -279,7 +321,7 @@ s.index[0]:select(nil, {iterator = 'REQ'})
   - [2, 1, 2, 3]
   - [1, 1, 2, 3]
 ...
-s.index[0]:select({}, {iterator = box.index.REQ})
+test.select(s.index[0], {}, {iterator = box.index.REQ})
 ---
 - - [20, 1, 2, 3]
   - [19, 1, 2, 3]
@@ -302,45 +344,45 @@ s.index[0]:select({}, {iterator = box.index.REQ})
   - [2, 1, 2, 3]
   - [1, 1, 2, 3]
 ...
-s.index[0]:select(nil, {iterator = 'EQ', limit = 2, offset = 1})
+test.select(s.index[0], nil, {iterator = 'EQ', limit = 2, offset = 1})
 ---
 - - [2, 1, 2, 3]
   - [3, 1, 2, 3]
 ...
-s.index[0]:select({}, {iterator = box.index.REQ, limit = 2, offset = 1})
+test.select(s.index[0], {}, {iterator = box.index.REQ, limit = 2, offset = 1})
 ---
 - - [19, 1, 2, 3]
   - [18, 1, 2, 3]
 ...
-s.index[0]:select(1)
+test.select(s.index[0], 1)
 ---
 - - [1, 1, 2, 3]
 ...
-s.index[0]:select({1})
+test.select(s.index[0], {1})
 ---
 - - [1, 1, 2, 3]
 ...
-s.index[0]:select({1, 2})
+test.select(s.index[0], {1, 2})
 ---
 - error: Invalid key part count (expected [0..1], got 2)
 ...
-s.index[0]:select(0)
+test.select(s.index[0], 0)
 ---
 - []
 ...
-s.index[0]:select({0})
+test.select(s.index[0], {0})
 ---
 - []
 ...
-s.index[0]:select("0")
+test.select(s.index[0], "0")
 ---
 - error: 'Supplied key type of part 0 does not match index part type: expected NUM'
 ...
-s.index[0]:select({"0"})
+test.select(s.index[0], {"0"})
 ---
 - error: 'Supplied key type of part 0 does not match index part type: expected NUM'
 ...
-s.index[1]:select(1)
+test.select(s.index[1], 1)
 ---
 - - [1, 1, 2, 3]
   - [2, 1, 2, 3]
@@ -363,7 +405,7 @@ s.index[1]:select(1)
   - [19, 1, 2, 3]
   - [20, 1, 2, 3]
 ...
-s.index[1]:select({1})
+test.select(s.index[1], {1})
 ---
 - - [1, 1, 2, 3]
   - [2, 1, 2, 3]
@@ -386,12 +428,12 @@ s.index[1]:select({1})
   - [19, 1, 2, 3]
   - [20, 1, 2, 3]
 ...
-s.index[1]:select({1}, {limit = 2})
+test.select(s.index[1], {1}, {limit = 2})
 ---
 - - [1, 1, 2, 3]
   - [2, 1, 2, 3]
 ...
-s.index[1]:select(1, {iterator = 'EQ'})
+test.select(s.index[1], 1, {iterator = 'EQ'})
 ---
 - - [1, 1, 2, 3]
   - [2, 1, 2, 3]
@@ -414,29 +456,29 @@ s.index[1]:select(1, {iterator = 'EQ'})
   - [19, 1, 2, 3]
   - [20, 1, 2, 3]
 ...
-s.index[1]:select({1}, {iterator = box.index.EQ, offset = 16, limit = 2})
+test.select(s.index[1], {1}, {iterator = box.index.EQ, offset = 16, limit = 2})
 ---
 - - [17, 1, 2, 3]
   - [18, 1, 2, 3]
 ...
-s.index[1]:select({1}, {iterator = box.index.REQ, offset = 16, limit = 2 })
+test.select(s.index[1], {1}, {iterator = box.index.REQ, offset = 16, limit = 2 })
 ---
 - - [4, 1, 2, 3]
   - [3, 1, 2, 3]
 ...
-s.index[1]:select({1, 2}, {iterator = 'EQ'})
+test.select(s.index[1], {1, 2}, {iterator = 'EQ'})
 ---
 - - [2, 1, 2, 3]
 ...
-s.index[1]:select({1, 2}, {iterator = box.index.REQ})
+test.select(s.index[1], {1, 2}, {iterator = box.index.REQ})
 ---
 - - [2, 1, 2, 3]
 ...
-s.index[1]:select({1, 2})
+test.select(s.index[1], {1, 2})
 ---
 - - [2, 1, 2, 3]
 ...
-s.index[0]:select(nil, { iterator = 'ALL', offset = 0, limit = 4294967295 })
+test.select(s.index[0], nil, { iterator = 'ALL', offset = 0, limit = 4294967295 })
 ---
 - - [1, 1, 2, 3]
   - [2, 1, 2, 3]
@@ -459,7 +501,7 @@ s.index[0]:select(nil, { iterator = 'ALL', offset = 0, limit = 4294967295 })
   - [19, 1, 2, 3]
   - [20, 1, 2, 3]
 ...
-s.index[0]:select({}, { iterator = 'ALL', offset = 0, limit = 4294967295 })
+test.select(s.index[0], {}, { iterator = 'ALL', offset = 0, limit = 4294967295 })
 ---
 - - [1, 1, 2, 3]
   - [2, 1, 2, 3]
@@ -482,19 +524,19 @@ s.index[0]:select({}, { iterator = 'ALL', offset = 0, limit = 4294967295 })
   - [19, 1, 2, 3]
   - [20, 1, 2, 3]
 ...
-s.index[0]:select(1)
+test.select(s.index[0], 1)
 ---
 - - [1, 1, 2, 3]
 ...
-s.index[0]:select(1, { iterator = box.index.EQ })
+test.select(s.index[0], 1, { iterator = box.index.EQ })
 ---
 - - [1, 1, 2, 3]
 ...
-s.index[0]:select(1, { iterator = 'EQ' })
+test.select(s.index[0], 1, { iterator = 'EQ' })
 ---
 - - [1, 1, 2, 3]
 ...
-s.index[0]:select(1, { iterator = 'GE' })
+test.select(s.index[0], 1, { iterator = 'GE' })
 ---
 - - [1, 1, 2, 3]
   - [2, 1, 2, 3]
@@ -517,16 +559,16 @@ s.index[0]:select(1, { iterator = 'GE' })
   - [19, 1, 2, 3]
   - [20, 1, 2, 3]
 ...
-s.index[0]:select(1, { iterator = 'GE', limit = 2 })
+test.select(s.index[0], 1, { iterator = 'GE', limit = 2 })
 ---
 - - [1, 1, 2, 3]
   - [2, 1, 2, 3]
 ...
-s.index[0]:select(1, { iterator = 'LE', limit = 2 })
+test.select(s.index[0], 1, { iterator = 'LE', limit = 2 })
 ---
 - - [1, 1, 2, 3]
 ...
-s.index[0]:select(1, { iterator = 'GE', offset = 10, limit = 2 })
+test.select(s.index[0], 1, { iterator = 'GE', offset = 10, limit = 2 })
 ---
 - - [11, 1, 2, 3]
   - [12, 1, 2, 3]
@@ -535,6 +577,43 @@ s:select(2)
 ---
 - - [2, 1, 2, 3]
 ...
+--------------------------------------------------------------------------------
+-- min/max tests
+--------------------------------------------------------------------------------
+test.min(s.index[1])
+---
+- [1, 1, 2, 3]
+...
+test.max(s.index[1])
+---
+- [20, 1, 2, 3]
+...
+--------------------------------------------------------------------------------
+-- count tests
+--------------------------------------------------------------------------------
+test.count(s.index[1])
+---
+- 20
+...
+test.count(s.index[0], nil)
+---
+- 20
+...
+test.count(s.index[0], {})
+---
+- 20
+...
+test.count(s.index[0], 10, { iterator = 'GT'})
+---
+- 10
+...
+--------------------------------------------------------------------------------
+-- random tests
+--------------------------------------------------------------------------------
+test.random(s.index[0], 48)
+---
+- [9, 1, 2, 3]
+...
 s:drop()
 ---
 ...
diff --git a/test/box/select.test.lua b/test/box/select.test.lua
index 2ac00390ff93c1d046dba40c749b81a9d48af898..d36d866f59d121f9ede0b8f4636a5b98877acbcb 100644
--- a/test/box/select.test.lua
+++ b/test/box/select.test.lua
@@ -1,80 +1,124 @@
+msgpack = require('msgpack')
+
 s = box.schema.space.create('select', { temporary = true })
 index1 = s:create_index('primary', { type = 'tree' })
 index2 = s:create_index('second', { type = 'tree', unique = true,  parts = {2, 'num', 1, 'num'}})
 for i = 1, 20 do s:insert({ i, 1, 2, 3 }) end
 
+--# setopt delimiter ';'
+local function test_op(op, idx, ...)
+    local t1 = idx[op .. '_ffi'](idx, ...)
+    local t2 = idx[op .. '_luac'](idx, ...)
+    if msgpack.encode(t1) ~= msgpack.encode(t2) then
+        return 'different result from '..op..'_ffi and '..op..'_luac', t1, t2
+    end
+    return t1
+end
+test = setmetatable({}, {
+    __index = function(_, op) return function(...) return test_op(op, ...) end end
+})
+--# setopt delimiter ''
+
+
 --------------------------------------------------------------------------------
 -- get tests
 --------------------------------------------------------------------------------
 
-s.index[0]:get()
-s.index[0]:get({})
-s.index[0]:get(nil)
-s.index[0]:get(1)
-s.index[0]:get({1})
-s.index[0]:get({1, 2})
-s.index[0]:get(0)
-s.index[0]:get({0})
-s.index[0]:get("0")
-s.index[0]:get({"0"})
-
-s.index[1]:get(1)
-s.index[1]:get({1})
-s.index[1]:get({1, 2})
+s.index[0].get == s.index[0].get_ffi or s.index[0].get == s.index[0].get_luac
+
+test.get(s.index[0])
+test.get(s.index[0], {})
+test.get(s.index[0], nil)
+test.get(s.index[0], 1)
+test.get(s.index[0], {1})
+test.get(s.index[0], {1, 2})
+test.get(s.index[0], 0)
+test.get(s.index[0], {0})
+test.get(s.index[0], "0")
+test.get(s.index[0], {"0"})
+
+test.get(s.index[1], 1)
+test.get(s.index[1], {1})
+test.get(s.index[1], {1, 2})
 
 --------------------------------------------------------------------------------
 -- select tests
 --------------------------------------------------------------------------------
 
-s.index[0]:select()
-s.index[0]:select({})
-s.index[0]:select(nil)
-s.index[0]:select({}, {iterator = 'ALL'})
-s.index[0]:select(nil, {iterator = box.index.ALL })
-s.index[0]:select({}, {iterator = box.index.ALL, limit = 10})
-s.index[0]:select(nil, {iterator = box.index.ALL, limit = 0})
-s.index[0]:select({}, {iterator = 'ALL', limit = 1, offset = 15})
-s.index[0]:select(nil, {iterator = 'ALL', limit = 20, offset = 15})
-
-s.index[0]:select(nil, {iterator = box.index.EQ})
-s.index[0]:select({}, {iterator = 'EQ'})
-s.index[0]:select(nil, {iterator = 'REQ'})
-s.index[0]:select({}, {iterator = box.index.REQ})
-
-s.index[0]:select(nil, {iterator = 'EQ', limit = 2, offset = 1})
-s.index[0]:select({}, {iterator = box.index.REQ, limit = 2, offset = 1})
-
-s.index[0]:select(1)
-s.index[0]:select({1})
-s.index[0]:select({1, 2})
-s.index[0]:select(0)
-s.index[0]:select({0})
-s.index[0]:select("0")
-s.index[0]:select({"0"})
-
-s.index[1]:select(1)
-s.index[1]:select({1})
-s.index[1]:select({1}, {limit = 2})
-s.index[1]:select(1, {iterator = 'EQ'})
-s.index[1]:select({1}, {iterator = box.index.EQ, offset = 16, limit = 2})
-s.index[1]:select({1}, {iterator = box.index.REQ, offset = 16, limit = 2 })
-s.index[1]:select({1, 2}, {iterator = 'EQ'})
-s.index[1]:select({1, 2}, {iterator = box.index.REQ})
-s.index[1]:select({1, 2})
-
-s.index[0]:select(nil, { iterator = 'ALL', offset = 0, limit = 4294967295 })
-s.index[0]:select({}, { iterator = 'ALL', offset = 0, limit = 4294967295 })
-
-s.index[0]:select(1)
-s.index[0]:select(1, { iterator = box.index.EQ })
-s.index[0]:select(1, { iterator = 'EQ' })
-s.index[0]:select(1, { iterator = 'GE' })
-s.index[0]:select(1, { iterator = 'GE', limit = 2 })
-s.index[0]:select(1, { iterator = 'LE', limit = 2 })
-s.index[0]:select(1, { iterator = 'GE', offset = 10, limit = 2 })
+s.index[0].select == s.index[0].select_ffi or s.index[0].select == s.index[0].select_luac
+
+test.select(s.index[0])
+test.select(s.index[0], {})
+test.select(s.index[0], nil)
+test.select(s.index[0], {}, {iterator = 'ALL'})
+
+test.select(s.index[0], nil, {iterator = box.index.ALL })
+test.select(s.index[0], {}, {iterator = box.index.ALL, limit = 10})
+test.select(s.index[0], nil, {iterator = box.index.ALL, limit = 0})
+test.select(s.index[0], {}, {iterator = 'ALL', limit = 1, offset = 15})
+test.select(s.index[0], nil, {iterator = 'ALL', limit = 20, offset = 15})
+
+test.select(s.index[0], nil, {iterator = box.index.EQ})
+test.select(s.index[0], {}, {iterator = 'EQ'})
+test.select(s.index[0], nil, {iterator = 'REQ'})
+test.select(s.index[0], {}, {iterator = box.index.REQ})
+
+test.select(s.index[0], nil, {iterator = 'EQ', limit = 2, offset = 1})
+test.select(s.index[0], {}, {iterator = box.index.REQ, limit = 2, offset = 1})
+
+test.select(s.index[0], 1)
+test.select(s.index[0], {1})
+test.select(s.index[0], {1, 2})
+test.select(s.index[0], 0)
+test.select(s.index[0], {0})
+test.select(s.index[0], "0")
+test.select(s.index[0], {"0"})
+
+test.select(s.index[1], 1)
+test.select(s.index[1], {1})
+test.select(s.index[1], {1}, {limit = 2})
+test.select(s.index[1], 1, {iterator = 'EQ'})
+test.select(s.index[1], {1}, {iterator = box.index.EQ, offset = 16, limit = 2})
+test.select(s.index[1], {1}, {iterator = box.index.REQ, offset = 16, limit = 2 })
+test.select(s.index[1], {1, 2}, {iterator = 'EQ'})
+test.select(s.index[1], {1, 2}, {iterator = box.index.REQ})
+test.select(s.index[1], {1, 2})
+
+test.select(s.index[0], nil, { iterator = 'ALL', offset = 0, limit = 4294967295 })
+test.select(s.index[0], {}, { iterator = 'ALL', offset = 0, limit = 4294967295 })
+
+test.select(s.index[0], 1)
+test.select(s.index[0], 1, { iterator = box.index.EQ })
+test.select(s.index[0], 1, { iterator = 'EQ' })
+test.select(s.index[0], 1, { iterator = 'GE' })
+test.select(s.index[0], 1, { iterator = 'GE', limit = 2 })
+test.select(s.index[0], 1, { iterator = 'LE', limit = 2 })
+test.select(s.index[0], 1, { iterator = 'GE', offset = 10, limit = 2 })
 
 s:select(2)
 
+--------------------------------------------------------------------------------
+-- min/max tests
+--------------------------------------------------------------------------------
+
+test.min(s.index[1])
+test.max(s.index[1])
+
+--------------------------------------------------------------------------------
+-- count tests
+--------------------------------------------------------------------------------
+
+test.count(s.index[1])
+test.count(s.index[0], nil)
+test.count(s.index[0], {})
+test.count(s.index[0], 10, { iterator = 'GT'})
+
+--------------------------------------------------------------------------------
+-- random tests
+--------------------------------------------------------------------------------
+
+test.random(s.index[0], 48)
+
 s:drop()
 
 s = box.schema.space.create('select', { temporary = true })