diff --git a/src/box/lua/index.cc b/src/box/lua/index.cc
index bb4d155dbbf0103a0c4e7eeb21bc72831307b318..aef6f2e2abb06256dc70de3ff336a614a640d8f1 100644
--- a/src/box/lua/index.cc
+++ b/src/box/lua/index.cc
@@ -42,6 +42,8 @@
 /** {{{ 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)
 {
@@ -307,6 +309,30 @@ boxffi_index_iterator(uint32_t space_id, uint32_t index_id, int type,
 	}
 }
 
+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)
 {
@@ -333,11 +359,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}
 	};
@@ -353,6 +404,8 @@ box_lua_index_init(struct lua_State *L)
 		{"min", lbox_index_min},
 		{"max", lbox_index_max},
 		{"count", lbox_index_count},
+		{"iterator", lbox_index_iterator},
+		{"iterator_next", lbox_iterator_next},
 		{NULL, NULL}
 	};
 
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 415e9928c3e3dc7a1fbed088a744104ad94a7975..fa392735e8142ba2d9934a0566d6cc8aa551fb82 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
@@ -650,7 +660,7 @@ function box.schema.space.bless(space)
         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);
 
@@ -660,11 +670,18 @@ 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_ffi = function(index, key, opts)
         local pkey, pkey_end = msgpackffi.encode_tuple(key)
@@ -764,7 +781,7 @@ function box.schema.space.bless(space)
 
     -- true if reading operations may yield
     local read_yields = space.engine == 'sophia'
-    local read_ops = {'select', 'get', 'min', 'max', 'count', 'random'}
+    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
@@ -774,6 +791,8 @@ function box.schema.space.bless(space)
             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/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