diff --git a/CMakeLists.txt b/CMakeLists.txt
index d1bf192c64876318d5f1d3870b186aaafe304e43..c025ea65ad309b58da5e493610c3e793a1b34777 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -12,6 +12,7 @@ find_program(CAT cat)
 find_program(GIT git)
 find_program(RAGEL ragel)
 find_program(CONFETTI confetti)
+find_program(LD ld)
 set(luadir ${PROJECT_BINARY_DIR}/third_party/luajit/src)
 link_directories(${luadir})
 
@@ -119,7 +120,7 @@ execute_process (COMMAND ${GIT} describe HEAD
 #
 set (CPACK_PACKAGE_VERSION_MAJOR "1")
 set (CPACK_PACKAGE_VERSION_MINOR "4")
-set (CPACK_PACKAGE_VERSION_PATCH "1")
+set (CPACK_PACKAGE_VERSION_PATCH "2")
 if (TARANTOOL_VERSION STREQUAL "")
     set (TARANTOOL_VERSION
         "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}")
diff --git a/cfg/CMakeLists.txt b/cfg/CMakeLists.txt
index 4e043d0a5ef03bd8267c33c73d090c6457f682e0..fe9e66f14199a84524b82a35f86ae7f326129f3c 100644
--- a/cfg/CMakeLists.txt
+++ b/cfg/CMakeLists.txt
@@ -30,12 +30,10 @@ macro(generate_config mod)
 # their sources when configuring the project.
 execute_process(COMMAND ${CMAKE_COMMAND} -E touch_nocreate
     ${CMAKE_SOURCE_DIR}/cfg/tarantool_${mod}_cfg.h
-    ${CMAKE_SOURCE_DIR}/cfg/tarantool_${mod}_cfg.c
-    ${CMAKE_SOURCE_DIR}/cfg/tarantool_${mod}_cfg.cfg)
+    ${CMAKE_SOURCE_DIR}/cfg/tarantool_${mod}_cfg.c)
 add_custom_command(
     OUTPUT ${CMAKE_SOURCE_DIR}/cfg/tarantool_${mod}_cfg.h
            ${CMAKE_SOURCE_DIR}/cfg/tarantool_${mod}_cfg.c
-           ${CMAKE_SOURCE_DIR}/cfg/tarantool_${mod}_cfg.cfg
     COMMAND ${ECHO} '%{' > ${mod}_tmp.cfg
     COMMAND ${ECHO} '\#include \"cfg/warning.h\"' >> ${mod}_tmp.cfg
     COMMAND ${ECHO} '\#include \"cfg/tarantool_${mod}_cfg.h\"' >> ${mod}_tmp.cfg
@@ -46,7 +44,6 @@ add_custom_command(
     COMMAND ${CONFETTI} -i ${mod}_tmp.cfg -n tarantool_cfg
             -c ${CMAKE_SOURCE_DIR}/cfg/tarantool_${mod}_cfg.c
             -h ${CMAKE_SOURCE_DIR}/cfg/tarantool_${mod}_cfg.h
-            -f ${CMAKE_SOURCE_DIR}/cfg/tarantool_${mod}_cfg.cfg
     COMMAND ${CMAKE_COMMAND} -E remove ${mod}_tmp.cfg
     DEPENDS ${CMAKE_SOURCE_DIR}/cfg/core_cfg.cfg_tmpl
             ${CMAKE_SOURCE_DIR}/cfg/warning.h
diff --git a/cfg/tarantool_box_cfg.cfg b/cfg/tarantool_box_cfg.cfg
deleted file mode 100644
index 3b2e6f843551889a9154ec3c4689613c94220008..0000000000000000000000000000000000000000
--- a/cfg/tarantool_box_cfg.cfg
+++ /dev/null
@@ -1,142 +0,0 @@
-
-# username to switch to
-username = NULL
-
-# tarantool bind ip address, applies to master
-# and replication ports. INADDR_ANY is the default value.
-bind_ipaddr = "INADDR_ANY"
-
-# save core on abort/assert
-# deprecated; use ulimit instead
-coredump = 0
-
-# admin port
-# used for admin's connections
-admin_port = 0
-
-# Replication clients should use this port (bind_ipaddr:replication_port).
-replication_port = 0
-
-# Log verbosity, possible values: ERROR=1, CRIT=2, WARN=3, INFO=4(default), DEBUG=5
-log_level = 4
-
-# Size of slab arena in GB
-slab_alloc_arena = 1
-
-# Size of minimal allocation unit
-slab_alloc_minimal = 64
-
-# Growth factor, each subsequent unit size is factor * prev unit size
-slab_alloc_factor = 2
-
-# working directory (daemon will chdir(2) to it)
-work_dir = NULL
-
-# name of pid file
-pid_file = "tarantool.pid"
-
-# logger command will be executed via /bin/sh -c {}
-# example: 'exec cronolog /var/log/tarantool/%Y-%m/%Y-%m-%d/tarantool.log'
-# example: 'exec extra/logger.pl /var/log/tarantool/tarantool.log'
-# when logger is not configured all logging going to STDERR
-logger = NULL
-
-# make logging nonblocking, this potentially can lose some logging data
-logger_nonblock = 1
-
-# delay between loop iterations
-io_collect_interval = 0
-
-# size of listen backlog
-backlog = 1024
-
-# network io readahead
-readahead = 16320
-
-# # BOX
-# Snapshot directory (where snapshots get saved/read)
-snap_dir = "."
-
-# WAL directory (where WALs get saved/read)
-wal_dir = "."
-
-# Primary port (where updates are accepted)
-primary_port = 0
-
-# Secondary port (where only selects are accepted)
-secondary_port = 0
-
-# Warn about requests which take longer to process, in seconds.
-too_long_threshold = 0.5
-
-# A custom process list (ps) title string, appended after the standard
-# program title.
-custom_proc_title = NULL
-
-# Memcached protocol support is enabled if memcached_port is set
-memcached_port = 0
-
-# namespace used for memcached emulation
-memcached_namespace = 23
-
-# Memcached expiration is on if memcached_expire is set.
-memcached_expire = 0
-
-# maximum rows to consider per expire loop iteration
-memcached_expire_per_loop = 1024
-
-# tarantool will try to iterate over all rows within this time
-memcached_expire_full_sweep = 3600
-
-# Do not write into snapshot faster than snap_io_rate_limit MB/sec
-snap_io_rate_limit = 0
-
-# Write no more rows in WAL
-rows_per_wal = 500000
-
-# fsync WAL delay, only issue fsync if last fsync was wal_fsync_delay
-# seconds ago.
-# WARNING: actually, several last requests may stall fsync for much longer
-wal_fsync_delay = 0
-
-# size of WAL writer request buffer
-wal_writer_inbox_size = 128
-
-# Local hot standby (if enabled, the server will run in hot
-# standby mode, continuously fetching WAL records from wal_dir,
-# until it is able to bind to the primary port.
-# In local hot standby mode the server only accepts reads.
-local_hot_standby = 0
-
-# Delay, in seconds, between successive re-readings of wal_dir.
-# The re-scan is necessary to discover new WAL files or snapshots.
-wal_dir_rescan_delay = 0.1
-
-# Panic if there is an error reading a snapshot or WAL.
-# By default, panic on any snapshot reading error and ignore errors
-# when reading WALs.
-panic_on_snap_error = 1
-panic_on_wal_error = 0
-
-# Replication mode (if enabled, the server, once
-# bound to the primary port, will connect to
-# replication_source (ipaddr:port) and run continously
-# fetching records from it.. In replication mode the server
-# only accepts reads.
-replication_source = NULL
-namespace = [    {
-        enabled = -1
-        cardinality = -1
-        estimated_rows = 0
-        index = [            {
-                type = ""
-                unique = -1
-                key_field = [                    {
-                        fieldno = -1
-                        type = ""
-                    }
-                ]
-            }
-        ]
-    }
-]
diff --git a/connector/c/CMakeLists.txt b/connector/c/CMakeLists.txt
index d5bd9b7d455c5432f5578380adc445bc84f8def8..4e27db259071bd52007dfdc301b19c1131bae4ab 100644
--- a/connector/c/CMakeLists.txt
+++ b/connector/c/CMakeLists.txt
@@ -15,7 +15,6 @@ if (${CMAKE_BUILD_TYPE} STREQUAL "Debug")
     set (tnt_cflags "${tnt_cflags} -Werror")
 endif()
 
-
 #============================================================================#
 # Build tnt projects
 #============================================================================#
@@ -45,6 +44,10 @@ set (tnt_sources
 # Builds
 #----------------------------------------------------------------------------#
 
+# Here we manage to build static/dynamic libraries ourselves,
+# do not use the top level settings.
+string(REPLACE "-static" "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
+
 #
 # Static library
 #
diff --git a/core/tarantool_lua.m b/core/tarantool_lua.m
index bd63ba3e757fcdea227b95b4c0015bfbf3e799b4..20ace99567999b55fdf09c902e7f3fc480b6af20 100644
--- a/core/tarantool_lua.m
+++ b/core/tarantool_lua.m
@@ -50,6 +50,21 @@ luaL_addvarint32(luaL_Buffer *b, u32 u32)
 	luaL_addlstring(b, tbuf.data, tbuf.len);
 }
 
+/* Convert box.pack() format specifier to Tarantool
+ * binary protocol UPDATE opcode
+ */
+static char format_to_opcode(char format)
+{
+	switch (format) {
+	case '=': return 0;
+	case '+': return 1;
+	case '&': return 2;
+	case '|': return 3;
+	case '^': return 4;
+	default: return format;
+	}
+}
+
 /**
  * To use Tarantool/Box binary protocol primitives from Lua, we
  * need a way to pack Lua variables into a binary representation.
@@ -63,7 +78,7 @@ luaL_addvarint32(luaL_Buffer *b, u32 u32)
  *
  * For example, a typical SELECT packet packs in Lua like this:
  *
- * pkt = box.pack("uuuuuup", -- pack format
+ * pkt = box.pack("iiiiiip", -- pack format
  *                         0, -- namespace id
  *                         0, -- index id
  *                         0, -- offset
@@ -81,41 +96,56 @@ lbox_pack(struct lua_State *L)
 	luaL_Buffer b;
 	const char *format = luaL_checkstring(L, 1);
 	int i = 2; /* first arg comes second */
+	int nargs = lua_gettop(L);
+	u32 u32buf;
+	size_t size;
+	const char *str;
 
 	luaL_buffinit(L, &b);
 
 	while (*format) {
+		if (i > nargs)
+			luaL_error(L, "box.pack: argument count does not match the format");
 		switch (*format) {
 		/* signed and unsigned 32-bit integers */
 		case 'I':
 		case 'i':
 		{
-			u32 u32 = luaL_checkinteger(L, i);
-			luaL_addlstring(&b, (const char *)&u32, sizeof(u32));
+			u32buf = lua_tointeger(L, i);
+			luaL_addlstring(&b, (char *) &u32buf, sizeof(u32));
 			break;
 		}
 		/* Perl 'pack' BER-encoded integer */
 		case 'w':
-			luaL_addvarint32(&b, luaL_checkinteger(L, i));
+			luaL_addvarint32(&b, lua_tointeger(L, i));
 			break;
 		/* A sequence of bytes */
 		case 'A':
 		case 'a':
-		{
-			size_t size;
-			const char *str = luaL_checklstring(L, i, &size);
+			str = luaL_checklstring(L, i, &size);
 			luaL_addlstring(&b, str, size);
 			break;
-		}
 		case 'P':
 		case 'p':
-		{
-			size_t size;
-			const char *str = luaL_checklstring(L, i, &size);
+			if (lua_type(L, i) == LUA_TNUMBER) {
+				u32buf= (u32) lua_tointeger(L, i);
+				str = (char *) &u32buf;
+				size = sizeof(u32);
+			} else {
+				str = luaL_checklstring(L, i, &size);
+			}
 			luaL_addvarint32(&b, size);
 			luaL_addlstring(&b, str, size);
 			break;
-		}
+		case '=': /* update tuple set foo=bar */
+		case '+': /* set field+=val */
+		case '&': /* set field&=val */
+		case '|': /* set field|=val */
+		case '^': /* set field^=val */
+			u32buf= (u32) lua_tointeger(L, i); /* field no */
+			luaL_addlstring(&b, (char *) &u32buf, sizeof(u32));
+			luaL_addchar(&b, format_to_opcode(*format));
+			break;
 		default:
 			luaL_error(L, "box.pack: unsupported pack "
 				   "format specifier '%c'", *format);
@@ -127,10 +157,36 @@ lbox_pack(struct lua_State *L)
 	return 1;
 }
 
+static int
+lbox_unpack(struct lua_State *L)
+{
+	const char *format = luaL_checkstring(L, 1);
+	int i = 2; /* first arg comes second */
+	int nargs = lua_gettop(L);
+	u32 u32buf;
+
+	while (*format) {
+		if (i > nargs)
+			luaL_error(L, "box.unpack: argument count does not match the format");
+		switch (*format) {
+		case 'i':
+			u32buf = * (u32 *) lua_tostring(L, i);
+			lua_pushnumber(L, u32buf);
+			break;
+		default:
+			luaL_error(L, "box.unpack: unsupported pack "
+				   "format specifier '%c'", *format);
+		} /* end switch */
+		i++;
+		format++;
+	}
+	return i-2;
+}
 /** A descriptor for box.tbuf object methods */
 
 static const struct luaL_reg boxlib[] = {
 	{"pack", lbox_pack},
+	{"unpack", lbox_unpack},
 	{NULL, NULL}
 };
 
@@ -138,9 +194,11 @@ static const struct luaL_reg boxlib[] = {
  * lua_tostring does no use __tostring metamethod, and it has
  * to be called if we want to print Lua userdata correctly.
  */
-static const char *
+const char *
 tarantool_lua_tostring(struct lua_State *L, int index)
 {
+	if (index < 0) /* we need an absolute index */
+		index = lua_gettop(L) + index + 1;
 	lua_getglobal(L, "tostring");
 	lua_pushvalue(L, index);
 	lua_call(L, 1, 1); /* pops both "tostring" and its argument */
@@ -251,9 +309,17 @@ tarantool_lua_dostring(struct lua_State *L, const char *str)
 	int r = luaL_loadstring(L, tbuf_str(buf));
 	if (r) {
 		lua_pop(L, 1); /* pop the error message */
-		return luaL_dostring(L, str);
+		r = luaL_loadstring(L, str);
+		if (r)
+			return r;
+	}
+	@try {
+		lua_call(L, 0, LUA_MULTRET);
+	} @catch (ClientError *e) {
+		lua_pushstring(L, e->errmsg);
+		return 1;
 	}
-	return lua_pcall(L, 0, LUA_MULTRET, 0);
+	return 0;
 }
 
 void
diff --git a/doc/user/language-reference.xml b/doc/user/language-reference.xml
index 5163f54e41c8a9898efba6480d849b58c55f85aa..48a92d5382d627c906b7cce6a64b7fa6e01c5329 100644
--- a/doc/user/language-reference.xml
+++ b/doc/user/language-reference.xml
@@ -118,8 +118,76 @@
 <section>
   <title>Writing stored procedures in Lua</title>
   <para>
-    Lua.
+    Lua is a light-weight, multi-paradigm embeddable language.
+    Tarantool/Box supports allows user to dynamically define,
+    alter, drop using the administrative console.
+    The procedures can be invoked Lua both from the administrative
+    console and using a binary protocol, for example:
+<programlisting>
+tarantool> lua function f1() return 'hello' end
+---
+...
+tarantool> call f1()
+Found 1 tuple:
+['hello']
+</programlisting>
+  </para>
+  <para>
+    There is a single global Lua interpreter state, which is
+    shared across all connections. Each connection, however, is
+    running in its own Lua <quote>thread</quote> -- a mechanism, akin to
+    Tarantool <quote>fibers</quote>.
+    Anything, prefixed with "lua " on the administrative console
+    is sent directly to the interpreter. In the binary protocol,
+    however, it is only possible to invoke Lua functions, but not
+    define or modify them.
+    A special command code designates invocation of a stored
+    program in the binary protocol. The tuple, sent as argument
+    of the command, is passed into the stored procedure, each
+    field of the tuple converted to a string parameter of the
+    procedure. As long as currently Tarantool tuples are
+    type-agnostic, Lua strings is chosen as the transport means
+    between the server and the interpreter.
+  </para>
+  <para>
+    Everything value, returned from a stored function by means of
+    <quote>return</quote> clause, is converted to Tarantool/Box tuple
+    and sent back to the client in binary form.
   </para>
+  <para>
+    It's possible not only to invoke trivial Lua code, but call
+    into Tarantool/Box storage functionality, using <quote>box</quote>
+    Lua library.
+    The main means of communication between Lua and Tarantool
+    is <quote>box.process()</quote> function, which allows
+    to send any kind of request to the server in the binary form.
+    Function <quote>box.process()</quote> is a server-side outlet
+    for Tarantool binary protocol. Any tuple returned by the
+    server is converted to a Lua object of tupe <quote>box.tuple</quote>
+    and appended to the return list of <quote>box.process()</quote>.
+  </para>
+  <para>
+    A few wrappers are defined to simplify the most common
+    tasks:
+    <itemizedlist>
+        <listitem><para><quote>box.select(namespace, key, ...)</quote>
+        to retrieve tuples. </para></listitem>
+        <listitem><para><quote>box.replace(namespace, ...)</quote>
+        to insert and replace tuples. The tuple is constructed
+        from all the remaining arguments passed into the function.</para></listitem>
+        <listitem><para><quote>box.update(namespace, key, tuple)</quote> and <quote>box.delete(namespace, key)</quote>for updates and deletes respectively.</para></listitem>
+    </itemizedlist>
+    The Lua source code of these wrappers, as well as a more
+    extensive documentation can be found in <filename>mod/box/box.lua</filename> file in the source tree.
+  </para>
+  <section>
+  <title>Replication of stored procedures</title>
+  <para>
+    The CALL statement itself does not enter Tarantool write ahead
+    log. Instead, the actual updates and deletes, performed by
+    the procedure, generate their own log events.
+  </para>
+  </section>
 </section>
 
 
diff --git a/include/tarantool.h b/include/tarantool.h
index 8eb90dcc512ea157d5b4ac36317d4523095008d3..980d171585c315961d4c44f691e8ddf54e6bc744 100644
--- a/include/tarantool.h
+++ b/include/tarantool.h
@@ -67,6 +67,9 @@ struct lua_State *tarantool_lua_init();
  * Created with tarantool_lua_init().
  */
 extern struct lua_State *tarantool_L;
+/* Call Lua 'tostring' built-in to print userdata nicely. */
+const char *
+tarantool_lua_tostring(struct lua_State *L, int index);
 
 extern struct tarantool_cfg cfg;
 extern const char *cfg_filename;
diff --git a/mod/box/CMakeLists.txt b/mod/box/CMakeLists.txt
index 09cab62f2c02b81034a5e8a7e8aee5cb9fbb0189..01f9535b38f5c73fb6ac15cc1cef1cc74910d158 100644
--- a/mod/box/CMakeLists.txt
+++ b/mod/box/CMakeLists.txt
@@ -4,17 +4,26 @@ add_custom_command(OUTPUT ${CMAKE_SOURCE_DIR}/mod/box/memcached-grammar.m
                      -o mod/box/memcached-grammar.m
     DEPENDS ${CMAKE_SOURCE_DIR}/mod/box/memcached-grammar.rl)
 # Do not clean memcached-grammar.m in 'make clean'.
-set_property(DIRECTORY PROPERTY CLEAN_NO_CUSTOM 1)
+set_property(DIRECTORY PROPERTY CLEAN_NO_CUSTOM true)
+set_property(DIRECTORY PROPERTY ADDITIONAL_MAKE_CLEAN_FILES box.lua.o)
 
 # Do not try to randomly re-generate memcached-grammar.m
 # after a fresh checkout/branch switch.
 execute_process(COMMAND ${CMAKE_COMMAND} -E touch_nocreate
     ${CMAKE_SOURCE_DIR}/mod/box/memcached-grammar.m)
 
+add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/box.lua.o
+    COMMAND ${LD} -r -b binary box.lua -o ${CMAKE_CURRENT_BINARY_DIR}/box.lua.o
+    DEPENDS box.lua)
+
+set_source_files_properties(box.lua.o
+    PROPERTIES EXTERNAL_OBJECT true)
+
 set_source_files_properties(memcached-grammar.m
     PROPERTIES HEADER_FILE_ONLY true)
 
 set_source_files_properties(memcached.m
     PROPERTIES COMPILE_FLAGS "-Wno-uninitialized")
 
-tarantool_module("box" tuple.m index.m box.m box_lua.m memcached.m memcached-grammar.m)
+tarantool_module("box" tuple.m index.m box.m box_lua.m memcached.m memcached-grammar.m
+    box.lua.o)
diff --git a/mod/box/box.h b/mod/box/box.h
index 8ca28ca9201d55b9dd8cd7cc8e3126baabd1cdad..986b7d673a2613c78a2c3fd88b7e0f2d73c67d28 100644
--- a/mod/box/box.h
+++ b/mod/box/box.h
@@ -58,7 +58,6 @@ struct box_out {
 };
 
 extern struct box_out box_out_quiet;
-extern struct box_out box_out_iproto;
 
 struct box_txn {
 	u16 op;
@@ -101,18 +100,18 @@ struct box_txn {
         _(DELETE, 8)
         _(UPDATE_FIELDS, 9)
         _(INSERT,10)
+        _(JUBOX_ALIVE, 11)
         _(SELECT_LIMIT, 12)
         _(SELECT_OLD, 14)
+        _(SELECT_LIMIT, 15)
         _(UPDATE_FIELDS_OLD, 16)
-        _(JUBOX_ALIVE, 11)
 
     DO NOT use these ids!
  */
 #define MESSAGES(_)				\
-        _(INSERT, 13)				\
-        _(SELECT_LIMIT, 15)			\
+        _(REPLACE, 13)				\
 	_(SELECT, 17)				\
-	_(UPDATE_FIELDS, 19)			\
+	_(UPDATE, 19)				\
 	_(DELETE_1_3, 20)			\
 	_(DELETE, 21)				\
 	_(CALL, 22)
diff --git a/mod/box/box.lua b/mod/box/box.lua
new file mode 100644
index 0000000000000000000000000000000000000000..07fce68538534a688ea903b483cbea9f52694fad
--- /dev/null
+++ b/mod/box/box.lua
@@ -0,0 +1,51 @@
+--
+--
+--
+function box.select(namespace, index, ...)
+    key = {...}
+    return select(2, -- skip the first return from select, number of tuples
+        box.process(17, box.pack('iiiiii'..string.rep('p', #key),
+                                 namespace,
+                                 index,
+                                 0, -- offset
+                                 4294967295, -- limit
+                                 1, -- key count
+                                 #key, -- key cardinality
+                                 unpack(key))))
+end
+--
+-- delete can be done only by the primary key, whose
+-- index is always 0. It doesn't accept compound keys
+--
+function box.delete(namespace, key)
+    return select(2, -- skip the first return, tuple count
+        box.process(21, box.pack('iiip', namespace,
+                                 1, -- flags, BOX_RETURN_TUPLE
+                                 1, -- cardinality
+                                 key)))
+end
+
+-- insert or replace a tuple
+function box.replace(namespace, ...)
+    tuple = {...}
+    return select(2,
+        box.process(13, box.pack('iii'..string.rep('p', #tuple),
+                                 namespace,
+                                 1, -- flags, BOX_RETURN_TUPLE 
+                                 #tuple, -- cardinality
+                                 unpack(tuple))))
+end
+
+box.insert = box.replace
+
+function box.update(namespace, key, format, ...)
+    ops = {...}
+    return select(2,
+        box.process(19, box.pack('iiipi'..format,
+                                  namespace,
+                                  1, -- flags, BOX_RETURN_TUPLE
+                                  1, -- cardinality
+                                  key, -- primary key
+                                  #ops/2, -- op count
+                                  ...)))
+end
diff --git a/mod/box/box.m b/mod/box/box.m
index 73a88fb019bbc0bdf41a612412b326b382155d18..386735aa432e4ac53c169094c0aeae9613a1cab1 100644
--- a/mod/box/box.m
+++ b/mod/box/box.m
@@ -167,8 +167,8 @@ prepare_replace(struct box_txn *txn, size_t cardinality, struct tbuf *data)
 		/*
 		 * If the tuple doesn't exist, insert a GHOST
 		 * tuple in all indices in order to avoid a race
-		 * condition when another INSERT comes along:
-		 * a concurrent INSERT, UPDATE, or DELETE, returns
+		 * condition when another REPLACE comes along:
+		 * a concurrent REPLACE, UPDATE, or DELETE, returns
 		 * an error when meets a ghost tuple.
 		 *
 		 * Tuple reference counter will be incremented in
@@ -318,7 +318,7 @@ do_field_splice(struct tbuf *field, void *args_data, u32 args_data_size)
 }
 
 static void __attribute__((noinline))
-prepare_update_fields(struct box_txn *txn, struct tbuf *data)
+prepare_update(struct box_txn *txn, struct tbuf *data)
 {
 	struct tbuf **fields;
 	void *field;
@@ -426,7 +426,7 @@ prepare_update_fields(struct box_txn *txn, struct tbuf *data)
 out:
 	txn->out->dup_u32(tuples_affected);
 
-	if (txn->flags & BOX_RETURN_TUPLE)
+	if (txn->flags & BOX_RETURN_TUPLE && txn->tuple)
 		txn->out->add_tuple(txn->tuple);
 }
 
@@ -550,7 +550,7 @@ commit_delete(struct box_txn *txn)
 static bool
 op_is_select(u32 op)
 {
-	return op == SELECT || op == SELECT_LIMIT;
+	return op == SELECT || op == CALL;
 }
 
 static void
@@ -578,7 +578,7 @@ iov_add_tuple(struct box_tuple *tuple)
 	}
 }
 
-struct box_out box_out_iproto = {
+static struct box_out box_out_iproto = {
 	iov_add_u32,
 	iov_dup_u32,
 	iov_add_tuple
@@ -695,7 +695,7 @@ txn_rollback(struct box_txn *txn)
 
 		unlock_tuples(txn);
 
-		if (txn->op == INSERT)
+		if (txn->op == REPLACE)
 			rollback_replace(txn);
 	}
 
@@ -712,7 +712,7 @@ box_dispatch(struct box_txn *txn, struct tbuf *data)
 	say_debug("box_dispatch(%i)", txn->op);
 
 	switch (txn->op) {
-	case INSERT:
+	case REPLACE:
 		txn_assign_n(txn, data);
 		txn->flags |= read_u32(data) & BOX_ALLOWED_REQUEST_FLAGS;
 		cardinality = read_u32(data);
@@ -756,10 +756,10 @@ box_dispatch(struct box_txn *txn, struct tbuf *data)
 		break;
 	}
 
-	case UPDATE_FIELDS:
+	case UPDATE:
 		txn_assign_n(txn, data);
 		txn->flags |= read_u32(data) & BOX_ALLOWED_REQUEST_FLAGS;
-		prepare_update_fields(txn, data);
+		prepare_update(txn, data);
 		break;
 	case CALL:
 		txn->flags |= read_u32(data) & BOX_ALLOWED_REQUEST_FLAGS;
@@ -804,7 +804,7 @@ box_xlog_sprint(struct tbuf *buf, const struct tbuf *t)
 		    messages_strs[op], n);
 
 	switch (op) {
-	case INSERT:
+	case REPLACE:
 		flags = read_u32(b);
 		cardinality = read_u32(b);
 		if (b->len != valid_tuple(b, cardinality))
@@ -822,7 +822,7 @@ box_xlog_sprint(struct tbuf *buf, const struct tbuf *t)
 		tuple_print(buf, key_len, key);
 		break;
 
-	case UPDATE_FIELDS:
+	case UPDATE:
 		flags = read_u32(b);
 		key_len = read_u32(b);
 		key = read_field(b);
@@ -1051,7 +1051,7 @@ convert_snap_row_to_wal(struct tbuf *t)
 {
 	struct tbuf *r = tbuf_alloc(fiber->gc_pool);
 	struct box_snap_row *row = box_snap_row(t);
-	u16 op = INSERT;
+	u16 op = REPLACE;
 	u32 flags = 0;
 
 	tbuf_append(r, &op, sizeof(op));
@@ -1374,6 +1374,8 @@ mod_init(void)
 	title("loading");
 	atexit(mod_free);
 
+	box_lua_init();
+
 	/* initialization namespaces */
 	namespace_init();
 
@@ -1427,8 +1429,6 @@ mod_init(void)
 	if (cfg.memcached_port != 0)
 		fiber_server("memcached", cfg.memcached_port,
 			     memcached_handler, NULL, NULL);
-
-	box_lua_init();
 }
 
 int
diff --git a/mod/box/box_lua.m b/mod/box/box_lua.m
index 10ced13d3ef787f34d4e92676cb97e4c41fa5927..995bb2a866921f3892fe94282b60212a224fdfdd 100644
--- a/mod/box/box_lua.m
+++ b/mod/box/box_lua.m
@@ -39,6 +39,9 @@
 #include "pickle.h"
 #include "tuple.h"
 
+/* contents of box.lua */
+extern const char _binary_box_lua_start;
+
 /**
  * All box connections share the same Lua state. We use
  * Lua coroutines (lua_newthread()) to have multiple
@@ -143,36 +146,58 @@ static const struct luaL_reg lbox_tuple_meta [] = {
 void iov_add_ret(struct lua_State *L, int index)
 {
 	int type = lua_type(L, index);
+	struct box_tuple *tuple;
 	switch (type) {
 	case LUA_TNUMBER:
-		box_out_iproto.dup_u32((u32) lua_tointeger(L, index));
+	case LUA_TSTRING:
+	{
+		size_t len;
+		const char *str = lua_tolstring(L, index, &len);
+		tuple = tuple_alloc(len + varint32_sizeof(len));
+		tuple->cardinality = 1;
+		memcpy(save_varint32(tuple->data, len), str, len);
 		break;
-	case LUA_TUSERDATA:
+	}
+	case LUA_TNIL:
+	case LUA_TBOOLEAN:
 	{
-		struct box_tuple *tuple = lua_istuple(L, index);
-		if (tuple != NULL) {
-			box_out_iproto.add_tuple(tuple);
-			break;
-		}
+		const char *str = tarantool_lua_tostring(L, index);
+		size_t len = strlen(str);
+		tuple = tuple_alloc(len + varint32_sizeof(len));
+		tuple->cardinality = 1;
+		memcpy(save_varint32(tuple->data, len), str, len);
+		break;
 	}
+	case LUA_TUSERDATA:
+		tuple = lua_istuple(L, index);
+		if (tuple)
+			break;
 	default:
 		/*
-		 * LUA_TNONE, LUA_TNIL, LUA_TBOOLEAN, LUA_TSTRING,
-		 * LUA_TTABLE, LUA_THREAD, LUA_TFUNCTION
+		 * LUA_TNONE, LUA_TTABLE, LUA_THREAD, LUA_TFUNCTION
 		 */
 		tnt_raise(ClientError, :ER_PROC_RET, lua_typename(L, type));
 		break;
 	}
+	tuple_txn_ref(in_txn(), tuple);
+	iov_add(&tuple->bsize, tuple_len(tuple));
 }
 
-/** Add nargs elements on the top of Lua stack to fiber iov. */
+/**
+ * Add all elements from Lua stack to fiber iov.
+ *
+ * To allow clients to understand a complex return from
+ * a procedure, we are compatible with SELECT protocol,
+ * and return the number of return values first, and
+ * then each return value as a tuple.
+ */
 
-void iov_add_multret(struct lua_State *L, int nargs)
+void iov_add_multret(struct lua_State *L)
 {
-	while (nargs > 0) {
-		iov_add_ret(L, -nargs);
-		nargs--;
-	}
+	int nargs = lua_gettop(L);
+	iov_dup(&nargs, sizeof(u32));
+	for (int i = 1; i <= nargs; ++i)
+		iov_add_ret(L, i);
 }
 
 static void
@@ -206,14 +231,15 @@ static struct box_out box_out_lua = {
 
 /* }}} */
 
-static inline void
+static inline struct box_txn *
 txn_enter_lua(lua_State *L)
 {
-	struct box_txn *txn = in_txn();
-	if (txn == NULL)
-		txn = txn_begin();
+	struct box_txn *old_txn = in_txn();
+	fiber->mod_data.txn = NULL;
+	struct box_txn *txn = fiber->mod_data.txn = txn_begin();
 	txn->out = &box_out_lua;
 	txn->L = L;
+	return old_txn;
 }
 
 /**
@@ -246,16 +272,12 @@ static int lbox_process(lua_State *L)
 		return luaL_error(L, "box.process(CALL, ...) is not allowed");
 	}
 	int top = lua_gettop(L); /* to know how much is added by rw_callback */
+
+	struct box_txn *old_txn = txn_enter_lua(L);
 	@try {
-		txn_enter_lua(L);
 		rw_callback(op, &req);
-		/*
-		 * @todo: when multi-statement transactions are
-		 * implemented, restore the original txn->out here.
-		 */
-		assert(in_txn() == NULL);
-	} @catch (ClientError *e) {
-		return luaL_error(L, "%d:%s", e->errcode, e->errmsg);
+	} @finally {
+		fiber->mod_data.txn = old_txn;
 	}
 	return lua_gettop(L) - top;
 }
@@ -293,6 +315,8 @@ void box_lua_find(lua_State *L, const char *name, const char *name_end)
 		tnt_raise(ClientError, :ER_NO_SUCH_PROC,
 			  name_end - name, name);
 	}
+	if (index != LUA_GLOBALSINDEX)
+		lua_remove(L, index);
 }
 
 
@@ -317,14 +341,17 @@ void box_lua_call(struct box_txn *txn __attribute__((unused)),
 		u32 field_len = read_varint32(data);
 		void *field = read_str(data, field_len); /* proc name */
 		box_lua_find(L, field, field + field_len);
-		lua_checkstack(L, 1);
-		/* Push the rest of args (a tuple) as is. */
-		lua_pushlstring(L, data->data, data->len);
-
-		int top = lua_gettop(L);
-		lua_call(L, 1, LUA_MULTRET);
+		/* Push the rest of args (a tuple). */
+		u32 nargs = read_u32(data);
+		luaL_checkstack(L, nargs, "call: out of stack");
+		for (int i = 0; i < nargs; i++) {
+			field_len = read_varint32(data);
+			field = read_str(data, field_len);
+			lua_pushlstring(L, field, field_len);
+		}
+		lua_call(L, nargs, LUA_MULTRET);
 		/* Send results of the called procedure to the client. */
-		iov_add_multret(L, lua_gettop(L) - top);
+		iov_add_multret(L);
 	} @finally {
 		/*
 		 * Allow the used coro to be garbage collected.
@@ -345,6 +372,8 @@ mod_lua_init(struct lua_State *L)
 	lua_pushstring(L, tuplelib_name);
 	lua_setfield(L, -2, "__metatable");
 	luaL_register(L, NULL, lbox_tuple_meta);
+	/* Load box.lua */
+	(void) luaL_dostring(L, &_binary_box_lua_start);
 	return L;
 }
 
diff --git a/mod/box/memcached.m b/mod/box/memcached.m
index a135b05d3b720c0c769381ae683439fcfaec1f6f..7f8cd2342184e08f26b602515592aa3fd78a6047 100644
--- a/mod/box/memcached.m
+++ b/mod/box/memcached.m
@@ -105,7 +105,7 @@ store(void *key, u32 exptime, u32 flags, u32 bytes, u8 *data)
 	 * Use a box dispatch wrapper which handles correctly
 	 * read-only/read-write modes.
 	 */
-	rw_callback(INSERT, req);
+	rw_callback(REPLACE, req);
 }
 
 static void
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 5834c41b5c2e878f42e81adb9002fd07555bc6e3..15bb5c2e83d1698729f84149d7507307c07aa440 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -7,6 +7,6 @@ tarantool_client("box/connector" ${CMAKE_SOURCE_DIR}/test/box/connector.c)
 
 install (PROGRAMS tarantool DESTINATION bin)
 install (DIRECTORY lib DESTINATION bin)
-install (FILES ${CMAKE_SOURCE_DIR}/box/tarantool.cfg
-        ${CMAKE_SOURCE_DIR}/box/00000000000000000001.snap
+install (FILES ${CMAKE_SOURCE_DIR}/test/box/tarantool.cfg
+        ${CMAKE_SOURCE_DIR}/test/box/00000000000000000001.snap
         DESTINATION bin)
diff --git a/test/box/admin.result b/test/box/admin.result
index 99da6c3fdb8fa597aa13901ff8c0ba89c5bc0dc5..9dab733a04efc4ed2ae508a834c087f5724493f6 100644
--- a/test/box/admin.result
+++ b/test/box/admin.result
@@ -1,13 +1,12 @@
 show stat
 ---
 statistics:
-  INSERT:        { rps:  0    , total:  0           }
-  SELECT_LIMIT:  { rps:  0    , total:  0           }
-  SELECT:        { rps:  0    , total:  0           }
-  UPDATE_FIELDS: { rps:  0    , total:  0           }
-  DELETE_1_3:    { rps:  0    , total:  0           }
-  DELETE:        { rps:  0    , total:  0           }
-  CALL:          { rps:  0    , total:  0           }
+  REPLACE:    { rps:  0    , total:  0           }
+  SELECT:     { rps:  0    , total:  0           }
+  UPDATE:     { rps:  0    , total:  0           }
+  DELETE_1_3: { rps:  0    , total:  0           }
+  DELETE:     { rps:  0    , total:  0           }
+  CALL:       { rps:  0    , total:  0           }
 ...
 help
 ---
@@ -75,13 +74,12 @@ configuration:
 show stat
 ---
 statistics:
-  INSERT:        { rps:  0    , total:  0           }
-  SELECT_LIMIT:  { rps:  0    , total:  0           }
-  SELECT:        { rps:  0    , total:  0           }
-  UPDATE_FIELDS: { rps:  0    , total:  0           }
-  DELETE_1_3:    { rps:  0    , total:  0           }
-  DELETE:        { rps:  0    , total:  0           }
-  CALL:          { rps:  0    , total:  0           }
+  REPLACE:    { rps:  0    , total:  0           }
+  SELECT:     { rps:  0    , total:  0           }
+  UPDATE:     { rps:  0    , total:  0           }
+  DELETE_1_3: { rps:  0    , total:  0           }
+  DELETE:     { rps:  0    , total:  0           }
+  CALL:       { rps:  0    , total:  0           }
 ...
 insert into t0 values (1, 'tuple')
 Insert OK, 1 row affected
diff --git a/test/box/configuration.result b/test/box/configuration.result
index fcb3ae026187908c0bb4357f048684a718497dac..42375c7c1bdc30653b089c7cc55568f151a7b402 100644
--- a/test/box/configuration.result
+++ b/test/box/configuration.result
@@ -7,11 +7,10 @@
 show stat
 ---
 statistics:
-  INSERT:        { rps:  0    , total:  0           }
-  SELECT_LIMIT:  { rps:  0    , total:  0           }
-  SELECT:        { rps:  0    , total:  0           }
-  UPDATE_FIELDS: { rps:  0    , total:  0           }
-  DELETE_1_3:    { rps:  0    , total:  0           }
-  DELETE:        { rps:  0    , total:  0           }
-  CALL:          { rps:  0    , total:  0           }
+  REPLACE:    { rps:  0    , total:  0           }
+  SELECT:     { rps:  0    , total:  0           }
+  UPDATE:     { rps:  0    , total:  0           }
+  DELETE_1_3: { rps:  0    , total:  0           }
+  DELETE:     { rps:  0    , total:  0           }
+  CALL:       { rps:  0    , total:  0           }
 ...
diff --git a/test/box/fifo.lua b/test/box/fifo.lua
new file mode 100644
index 0000000000000000000000000000000000000000..e8915fad3e026901c6b16d02b9fd95c6c6391fab
--- /dev/null
+++ b/test/box/fifo.lua
@@ -0,0 +1,31 @@
+fifomax = 5
+function find_or_create_fifo(name)
+    fifo = box.select(0, 0, name)
+    if fifo == nil then
+        fifo = {}
+        for i = 1, fifomax do fifo[i] = 0 end
+        fifo = box.insert(0, name, 3, 3, unpack(fifo))
+    end
+    return fifo
+end
+function fifo_push(name, val)
+    fifo = find_or_create_fifo(name)
+    top = box.unpack('i', fifo[1])
+    bottom = box.unpack('i', fifo[2])
+    if top == fifomax+2 then -- % size
+        top = 3
+    elseif top ~= bottom then -- was not empty
+        top = top + 1
+    end
+    if bottom == fifomax + 2 then -- % size
+        bottom = 3
+    elseif bottom == top then
+        bottom = bottom + 1
+    end
+    return box.update(0, name, '=p=p=p', 1, top, 2, bottom, top, val)
+end
+function fifo_top(name)
+    fifo = find_or_create_fifo(name)
+    top = box.unpack('i', fifo[1])
+    return box.unpack('i', fifo[top])
+end
diff --git a/test/box/lua.result b/test/box/lua.result
index d706f2a7420cd3155a45af0698aa3cc9b4bdf57b..40ed053735cae9576e9e29ab961ef38f38c860a7 100644
--- a/test/box/lua.result
+++ b/test/box/lua.result
@@ -12,20 +12,26 @@ lua print('  lua says: hello')
 ...
 lua for n in pairs(box) do print('  - box.', n) end
 ---
-  - box.process
+  - box.delete
   - box.pack
+  - box.select
+  - box.insert
+  - box.unpack
+  - box.replace
+  - box.update
+  - box.process
 ...
 lua box.pack()
 ---
-error: 'bad argument #1 to ''?'' (string expected, got no value)'
+error: 'Lua error: bad argument #1 to ''?'' (string expected, got no value)'
 ...
 lua box.pack(1)
 ---
-error: 'box.pack: unsupported pack format specifier ''1'''
+error: 'Lua error: box.pack: argument count does not match the format'
 ...
 lua box.pack('abc')
 ---
-error: 'bad argument #2 to ''?'' (string expected, got no value)'
+error: 'Lua error: box.pack: argument count does not match the format'
 ...
 lua print(box.pack('a', ' - hello'))
 ---
@@ -45,40 +51,251 @@ lua print(box.pack('www', 0x30, 0x30, 0x30))
 ...
 lua print(box.pack('www', 0x3030, 0x30))
 ---
-error: '[string "return print(box.pack(''www'', 0x3030, 0x30))"]:1: bad argument #4 to ''pack'' (number expected, got no value)'
+error: 'Lua error: [string "return print(box.pack(''www'', 0x3030, 0x30))"]:1: box.pack: argument count does not match the format'
 ...
 lua print(string.byte(box.pack('w', 212345), 1, 2))
 ---
 140250
 ...
+lua print(string.sub(box.pack('p', 1684234849), 2))
+---
+abcd
+...
 lua print(box.pack('p', 'this string is 45 characters long 1234567890 '))
 ---
 -this string is 45 characters long 1234567890 
 ...
-lua box.process(13, box.pack('iiippp', 0, 1, 3, box.pack('i', 1), 'testing', 'lua rocks'))
+lua box.process(13, box.pack('iiippp', 0, 1, 3, 1, 'testing', 'lua rocks'))
 ---
  - 1
  - 1: {'testing', 'lua rocks'}
 ...
-lua box.process(17, box.pack('iiiiiip', 0, 0, 0, 2^31, 1, 1, box.pack('i', 1)))
+lua box.process(17, box.pack('iiiiiip', 0, 0, 0, 2^31, 1, 1, 1))
 ---
  - 0
  - 1: {'testing', 'lua rocks'}
 ...
-lua box.process(21, box.pack('iiip', 0, 1, 1, box.pack('i', 1)))
+lua box.process(21, box.pack('iiip', 0, 1, 1, 1))
 ---
  - 1
  - 1: {'testing', 'lua rocks'}
 ...
-lua box.process(17, box.pack('iiiiiip', 0, 0, 0, 2^31, 1, 1, box.pack('i', 1)))
+lua box.process(17, box.pack('iiiiiip', 0, 0, 0, 2^31, 1, 1, 1))
 ---
  - 0
 ...
 lua box.process(22, box.pack('iii', 0, 0, 0))
 ---
-error: 'box.process(CALL, ...) is not allowed'
+error: 'Lua error: box.process(CALL, ...) is not allowed'
 ...
 call box.process('abc', 'def')
-An error occurred: ER_PROC_LUA, 'Lua error: bad argument #2 to '?' (string expected, got no value)�'
+An error occurred: ER_ILLEGAL_PARAMS, 'Illegal parameters, unsupported command code, check the error log�'
 call box.pack('test')
-An error occurred: ER_PROC_LUA, 'Lua error: box.pack: unsupported pack format specifier ''�'
+An error occurred: ER_PROC_LUA, 'Lua error: box.pack: argument count does not match the format�'
+call box.pack('p', 'this string is 45 characters long 1234567890 ')
+Found 1 tuple:
+['-this string is 45 characters long 1234567890 ']
+call box.pack('p', 'ascii symbols are visible starting from code 20')
+Found 1 tuple:
+['/ascii symbols are visible starting from code 20']
+lua function f1() return 'testing', 1, false, -1, 1.123, 1e123, nil end
+---
+...
+lua f1()
+---
+ - testing
+ - 1
+ - false
+ - -1
+ - 1.123
+ - 1e+123
+ - nil
+...
+call f1()
+Found 7 tuples:
+['testing']
+['1']
+['false']
+['-1']
+['1.123']
+['1e+123']
+['nil']
+lua f1=nil
+---
+...
+call f1()
+An error occurred: ER_NO_SUCH_PROC, 'Procedure f1 is not defined�'
+lua function f1() return f1 end
+---
+...
+call f1()
+An error occurred: ER_PROC_RET, 'Return type 'function' is not supported in the binary protocol�'
+insert into t0 values (1, 'test box delete')
+Insert OK, 1 row affected
+call box.delete(0, '���')
+Found 1 tuple:
+[1, 'test box delete']
+call box.delete(0, '���')
+No match
+insert into t0 values (1, 'test box delete')
+Insert OK, 1 row affected
+lua box.delete(0, 1)
+---
+ - 1: {'test box delete'}
+...
+lua box.delete(0, 1)
+---
+...
+insert into t0 values ('abcd', 'test box delete')
+Insert OK, 1 row affected
+call box.delete(0, '���')
+No match
+call box.delete(0, 'abcd')
+Found 1 tuple:
+[1684234849, 'test box delete']
+call box.delete(0, 'abcd')
+No match
+insert into t0 values ('abcd', 'test box delete')
+Insert OK, 1 row affected
+lua box.delete(0, 'abcd')
+---
+ - 1684234849: {'test box delete'}
+...
+lua box.delete(0, 'abcd')
+---
+...
+call box.select(0, 0, 'abcd')
+No match
+insert into t0 values ('abcd', 'test box.select()')
+Insert OK, 1 row affected
+call box.select(0, 0, 'abcd')
+Found 1 tuple:
+[1684234849, 'test box.select()']
+lua box.select(0, 0, 'abcd')
+---
+ - 1684234849: {'test box.select()'}
+...
+lua box.select(0, 0)
+---
+error: 'Illegal parameters, key must be single valued'
+...
+lua box.select(0, 1)
+---
+error: 'No index #1 is defined in namespace 0'
+...
+lua box.select(0)
+---
+error: 'Illegal parameters, key must be single valued'
+...
+call box.replace(0, 'abcd', 'hello', 'world')
+Found 1 tuple:
+[1684234849, 'hello', 'world']
+call box.replace(0, 'defc', 'goodbye', 'universe')
+Found 1 tuple:
+[1667655012, 'goodbye', 'universe']
+call box.select(0, 0, 'abcd')
+Found 1 tuple:
+[1684234849, 'hello', 'world']
+call box.select(0, 0, 'defc')
+Found 1 tuple:
+[1667655012, 'goodbye', 'universe']
+call box.replace(0, 'abcd')
+Found 1 tuple:
+[1684234849]
+call box.select(0, 0, 'abcd')
+Found 1 tuple:
+[1684234849]
+call box.delete(0, 'abcd')
+Found 1 tuple:
+[1684234849]
+call box.delete(0, 'defc')
+Found 1 tuple:
+[1667655012, 'goodbye', 'universe']
+call box.insert(0, 'test', 'old', 'abcd')
+Found 1 tuple:
+[1953719668, 'old', 1684234849]
+call box.update(0, 'test', '=p=p', 0, 'pass', 1, 'new')
+Found 1 tuple:
+[1936941424, 'new', 1684234849]
+call box.select(0, 0, 'pass')
+Found 1 tuple:
+[1936941424, 'new', 1684234849]
+call box.update(0, 'miss', '+p', 2, '���')
+No match
+call box.update(0, 'pass', '+p', 2, '���')
+Found 1 tuple:
+[1936941424, 'new', 1684234850]
+lua box.update(0, 'pass', '+p', 2, 1)
+---
+ - 1936941424: {'new', 1684234851}
+...
+call box.select(0, 0, 'pass')
+Found 1 tuple:
+[1936941424, 'new', 1684234851]
+lua function field_x(namespace, key, field_index) return (box.select(namespace, 0, key))[field_index] end
+---
+...
+call field_x(0, 'pass', 0)
+Found 1 tuple:
+[1936941424]
+call field_x(0, 'pass', 1)
+Found 1 tuple:
+['new']
+call box.delete(0, 'pass')
+Found 1 tuple:
+[1936941424, 'new', 1684234851]
+lua dofile('/opt/local/work/tarantool-newlua/test/box/fifo.lua')
+---
+...
+lua fifo_max
+---
+ - nil
+...
+lua fifo_push('test', 1)
+---
+ - 1953719668: {3, 4, 1, 0, 0, 0, 0}
+...
+lua fifo_push('test', 2)
+---
+ - 1953719668: {4, 5, 1, 2, 0, 0, 0}
+...
+lua fifo_push('test', 3)
+---
+ - 1953719668: {5, 6, 1, 2, 3, 0, 0}
+...
+lua fifo_push('test', 4)
+---
+ - 1953719668: {6, 7, 1, 2, 3, 4, 0}
+...
+lua fifo_push('test', 5)
+---
+ - 1953719668: {7, 3, 1, 2, 3, 4, 5}
+...
+lua fifo_push('test', 6)
+---
+ - 1953719668: {3, 4, 6, 2, 3, 4, 5}
+...
+lua fifo_push('test', 7)
+---
+ - 1953719668: {4, 5, 6, 7, 3, 4, 5}
+...
+lua fifo_push('test', 8)
+---
+ - 1953719668: {5, 6, 6, 7, 8, 4, 5}
+...
+lua fifo_top('test')
+---
+ - 8
+...
+lua box.delete(0, 'test')
+---
+ - 1953719668: {5, 6, 6, 7, 8, 4, 5}
+...
+lua fifo_top('test')
+---
+ - 0
+...
+lua box.delete(0, 'test')
+---
+ - 1953719668: {3, 3, 0, 0, 0, 0, 0}
+...
diff --git a/test/box/lua.test b/test/box/lua.test
index 38b24311a825313c0fcf325408154052e7177ea8..58487ef3a121cc1fdef1e848e06b30e65422e952 100644
--- a/test/box/lua.test
+++ b/test/box/lua.test
@@ -1,4 +1,6 @@
 # encoding: tarantool
+import os
+import sys
 # Test Lua from admin console. Whenever producing output,
 # make sure it's a valid YAML.
 exec admin "lua"
@@ -16,17 +18,85 @@ exec admin "lua print(box.pack('w', 0x30))"
 exec admin "lua print(box.pack('www', 0x30, 0x30, 0x30))"
 exec admin "lua print(box.pack('www', 0x3030, 0x30))"
 exec admin "lua print(string.byte(box.pack('w', 212345), 1, 2))"
+exec admin "lua print(string.sub(box.pack('p', 1684234849), 2))"
 exec admin "lua print(box.pack('p', 'this string is 45 characters long 1234567890 '))"
 # Test the low-level box.process() call, which takes a binary packet
 # and passes it to box for execution.
 # insert:
-exec admin "lua box.process(13, box.pack('iiippp', 0, 1, 3, box.pack('i', 1), 'testing', 'lua rocks'))"
+exec admin "lua box.process(13, box.pack('iiippp', 0, 1, 3, 1, 'testing', 'lua rocks'))"
 # select:
-exec admin "lua box.process(17, box.pack('iiiiiip', 0, 0, 0, 2^31, 1, 1, box.pack('i', 1)))"
+exec admin "lua box.process(17, box.pack('iiiiiip', 0, 0, 0, 2^31, 1, 1, 1))"
 # delete:
-exec admin "lua box.process(21, box.pack('iiip', 0, 1, 1, box.pack('i', 1)))"
+exec admin "lua box.process(21, box.pack('iiip', 0, 1, 1, 1))"
 # check delete:
-exec admin "lua box.process(17, box.pack('iiiiiip', 0, 0, 0, 2^31, 1, 1, box.pack('i', 1)))"
+exec admin "lua box.process(17, box.pack('iiiiiip', 0, 0, 0, 2^31, 1, 1, 1))"
 exec admin "lua box.process(22, box.pack('iii', 0, 0, 0))"
 exec sql "call box.process('abc', 'def')"
 exec sql "call box.pack('test')"
+exec sql "call box.pack('p', 'this string is 45 characters long 1234567890 ')"
+exec sql "call box.pack('p', 'ascii symbols are visible starting from code 20')"
+exec admin "lua function f1() return 'testing', 1, false, -1, 1.123, 1e123, nil end"
+exec admin "lua f1()"
+exec sql "call f1()"
+exec admin "lua f1=nil"
+exec sql "call f1()"
+exec admin "lua function f1() return f1 end"
+exec sql "call f1()"
+
+exec sql "insert into t0 values (1, 'test box delete')"
+exec sql "call box.delete(0, '\1\0\0\0')"
+exec sql "call box.delete(0, '\1\0\0\0')"
+exec sql "insert into t0 values (1, 'test box delete')"
+exec admin "lua box.delete(0, 1)"
+exec admin "lua box.delete(0, 1)"
+exec sql "insert into t0 values ('abcd', 'test box delete')"
+exec sql "call box.delete(0, '\1\0\0\0')"
+exec sql "call box.delete(0, 'abcd')"
+exec sql "call box.delete(0, 'abcd')"
+exec sql "insert into t0 values ('abcd', 'test box delete')"
+exec admin "lua box.delete(0, 'abcd')"
+exec admin "lua box.delete(0, 'abcd')"
+exec sql "call box.select(0, 0, 'abcd')"
+exec sql "insert into t0 values ('abcd', 'test box.select()')"
+exec sql "call box.select(0, 0, 'abcd')"
+exec admin "lua box.select(0, 0, 'abcd')"
+exec admin "lua box.select(0, 0)"
+exec admin "lua box.select(0, 1)"
+exec admin "lua box.select(0)"
+exec sql "call box.replace(0, 'abcd', 'hello', 'world')"
+exec sql "call box.replace(0, 'defc', 'goodbye', 'universe')"
+exec sql "call box.select(0, 0, 'abcd')"
+exec sql "call box.select(0, 0, 'defc')"
+exec sql "call box.replace(0, 'abcd')"
+exec sql "call box.select(0, 0, 'abcd')"
+exec sql "call box.delete(0, 'abcd')"
+exec sql "call box.delete(0, 'defc')"
+exec sql "call box.insert(0, 'test', 'old', 'abcd')"
+exec sql "call box.update(0, 'test', '=p=p', 0, 'pass', 1, 'new')"
+exec sql "call box.select(0, 0, 'pass')"
+exec sql "call box.update(0, 'miss', '+p', 2, '\1\0\0\0')"
+exec sql "call box.update(0, 'pass', '+p', 2, '\1\0\0\0')"
+exec admin "lua box.update(0, 'pass', '+p', 2, 1)"
+exec sql "call box.select(0, 0, 'pass')"
+exec admin "lua function field_x(namespace, key, field_index) return (box.select(namespace, 0, key))[field_index] end"
+exec sql "call field_x(0, 'pass', 0)"
+exec sql "call field_x(0, 'pass', 1)"
+exec sql "call box.delete(0, 'pass')"
+fifo_lua = os.path.abspath("box/fifo.lua")
+# don't log the path name
+sys.stdout.push_filter("exec admin .*", "exec admin ...")
+exec admin "lua dofile('{0}')".format(fifo_lua)
+sys.stdout.pop_filter()
+exec admin "lua fifo_max"
+exec admin "lua fifo_push('test', 1)"
+exec admin "lua fifo_push('test', 2)"
+exec admin "lua fifo_push('test', 3)"
+exec admin "lua fifo_push('test', 4)"
+exec admin "lua fifo_push('test', 5)"
+exec admin "lua fifo_push('test', 6)"
+exec admin "lua fifo_push('test', 7)"
+exec admin "lua fifo_push('test', 8)"
+exec admin "lua fifo_top('test')"
+exec admin "lua box.delete(0, 'test')"
+exec admin "lua fifo_top('test')"
+exec admin "lua box.delete(0, 'test')"
diff --git a/test/box/stat.result b/test/box/stat.result
index 35383bd2f79d22db254e66a4afe6f1e345e6b2b8..37f80dd38859d4070850a2d8a8c5c0e774415fc1 100644
--- a/test/box/stat.result
+++ b/test/box/stat.result
@@ -26,13 +26,12 @@ Insert OK, 1 row affected
 show stat
 ---
 statistics:
-  INSERT:        { rps:  2    , total:  10          }
-  SELECT_LIMIT:  { rps:  0    , total:  0           }
-  SELECT:        { rps:  0    , total:  0           }
-  UPDATE_FIELDS: { rps:  0    , total:  0           }
-  DELETE_1_3:    { rps:  0    , total:  0           }
-  DELETE:        { rps:  0    , total:  0           }
-  CALL:          { rps:  0    , total:  0           }
+  REPLACE:    { rps:  2    , total:  10          }
+  SELECT:     { rps:  0    , total:  0           }
+  UPDATE:     { rps:  0    , total:  0           }
+  DELETE_1_3: { rps:  0    , total:  0           }
+  DELETE:     { rps:  0    , total:  0           }
+  CALL:       { rps:  0    , total:  0           }
 ...
 #
 # restart server
@@ -45,13 +44,12 @@ statistics:
 show stat
 ---
 statistics:
-  INSERT:        { rps:  0    , total:  0           }
-  SELECT_LIMIT:  { rps:  0    , total:  0           }
-  SELECT:        { rps:  0    , total:  0           }
-  UPDATE_FIELDS: { rps:  0    , total:  0           }
-  DELETE_1_3:    { rps:  0    , total:  0           }
-  DELETE:        { rps:  0    , total:  0           }
-  CALL:          { rps:  0    , total:  0           }
+  REPLACE:    { rps:  0    , total:  0           }
+  SELECT:     { rps:  0    , total:  0           }
+  UPDATE:     { rps:  0    , total:  0           }
+  DELETE_1_3: { rps:  0    , total:  0           }
+  DELETE:     { rps:  0    , total:  0           }
+  CALL:       { rps:  0    , total:  0           }
 ...
 delete from t0 where k0 = 0
 Delete OK, 1 row affected
diff --git a/test/box_big/lua.result b/test/box_big/lua.result
new file mode 100644
index 0000000000000000000000000000000000000000..c21c4e7a1f7110584e9033511cf1d58a731e3edd
--- /dev/null
+++ b/test/box_big/lua.result
@@ -0,0 +1,8 @@
+insert into t1 values ('brave', 'new', 'world')
+Insert OK, 1 row affected
+call box.select(1, 1, 'new', 'world')
+Found 1 tuple:
+['brave', 'new', 'world']
+call box.delete(1, 'brave')
+Found 1 tuple:
+['brave', 'new', 'world']
diff --git a/test/box_big/lua.test b/test/box_big/lua.test
new file mode 100644
index 0000000000000000000000000000000000000000..8f75aee5117f11d7f9b08268f20663243c1cc0f9
--- /dev/null
+++ b/test/box_big/lua.test
@@ -0,0 +1,4 @@
+# encoding: tarantool
+exec sql "insert into t1 values ('brave', 'new', 'world')"
+exec sql "call box.select(1, 1, 'new', 'world')"
+exec sql "call box.delete(1, 'brave')"
diff --git a/test/box_big/tarantool.cfg b/test/box_big/tarantool.cfg
index 510ac98441311ff37da80d8b813046a8553cddb0..2247dae5d07b1efad64f11e69f3009f4cf96ebef 100644
--- a/test/box_big/tarantool.cfg
+++ b/test/box_big/tarantool.cfg
@@ -19,3 +19,15 @@ namespace[0].index[1].type = "TREE"
 namespace[0].index[1].unique = 0
 namespace[0].index[1].key_field[0].fieldno = 1
 namespace[0].index[1].key_field[0].type = "STR"
+
+namespace[1].enabled = 1
+namespace[1].index[0].type = "HASH"
+namespace[1].index[0].unique = 1
+namespace[1].index[0].key_field[0].fieldno = 0
+namespace[1].index[0].key_field[0].type = "STR"
+namespace[1].index[1].type = "TREE"
+namespace[1].index[1].unique = 1
+namespace[1].index[1].key_field[0].fieldno = 1
+namespace[1].index[1].key_field[0].type = "STR"
+namespace[1].index[1].key_field[1].fieldno = 2
+namespace[1].index[1].key_field[1].type = "STR"
diff --git a/test/box_memcached/multiversioning.result b/test/box_memcached/multiversioning.result
index 5fccf79ed9a98a086c841b1e2577bdc065b9461b..390a36142461b70dae23e79c44efc74442688d88 100644
--- a/test/box_memcached/multiversioning.result
+++ b/test/box_memcached/multiversioning.result
@@ -18,10 +18,9 @@ success: buf == reply
 show stat
 ---
 statistics:
-  INSERT:            { rps:  0    , total:  0           }
-  SELECT_LIMIT:      { rps:  0    , total:  0           }
+  REPLACE:           { rps:  0    , total:  0           }
   SELECT:            { rps:  0    , total:  0           }
-  UPDATE_FIELDS:     { rps:  0    , total:  0           }
+  UPDATE:            { rps:  0    , total:  0           }
   DELETE_1_3:        { rps:  0    , total:  0           }
   DELETE:            { rps:  0    , total:  0           }
   CALL:              { rps:  0    , total:  0           }
diff --git a/test/lib/admin_connection.py b/test/lib/admin_connection.py
index 32562fcd9fd857c4576213e57ec62d411418333c..26cb244a4ea67ad66c898ff203fdaea9b5c4c222 100644
--- a/test/lib/admin_connection.py
+++ b/test/lib/admin_connection.py
@@ -27,7 +27,7 @@ import sys
 import re
 from tarantool_connection import TarantoolConnection
 
-is_admin_re = re.compile("^\s*(show|save|exec|exit|reload|help)", re.I)
+is_admin_re = re.compile("^\s*(show|save|lua|exit|reload|help)", re.I)
 
 ADMIN_SEPARATOR = '\n'
 
diff --git a/test/lib/sql.g b/test/lib/sql.g
index fd768357caace9953e5c8e7de14425ab84a16d01..2e41965d5681285c6626438bb1c80f1bb41efa4e 100644
--- a/test/lib/sql.g
+++ b/test/lib/sql.g
@@ -63,8 +63,8 @@ parser sql:
                       {{ return disjunction }}
     rule opt_limit:   {{ return 0xffffffff }}
                       | LIMIT NUM {{ return int(NUM) }}
-    rule value_list:  '\(' expr {{ value_list = [expr] }}
-                          [("," expr {{ value_list.append(expr) }} )+]
+    rule value_list:  '\(' {{ value_list = [] }}
+                         [expr {{ value_list = [expr] }} [("," expr {{ value_list.append(expr) }} )+]]
                       '\)' {{ return value_list }}
     rule update_list: predicate {{ update_list = [predicate] }}
                       [(',' predicate {{ update_list.append(predicate) }})+]
diff --git a/test/lib/sql.py b/test/lib/sql.py
index b9621195b994c69500b615bfc683854b02416635..ad5b92331629c14eea1e855c43480b60d9b64a1b 100644
--- a/test/lib/sql.py
+++ b/test/lib/sql.py
@@ -166,14 +166,16 @@ class sql(runtime.Parser):
     def value_list(self, _parent=None):
         _context = self.Context(_parent, self._scanner, 'value_list', [])
         self._scan("'\\('", context=_context)
-        expr = self.expr(_context)
-        value_list = [expr]
-        if self._peek('","', "'\\)'", context=_context) == '","':
-            while 1:
-                self._scan('","', context=_context)
-                expr = self.expr(_context)
-                value_list.append(expr)
-                if self._peek('","', "'\\)'", context=_context) != '","': break
+        value_list = []
+        if self._peek("'\\)'", '","', 'NUM', 'STR', context=_context) in ['NUM', 'STR']:
+            expr = self.expr(_context)
+            value_list = [expr]
+            if self._peek('","', "'\\)'", context=_context) == '","':
+                while 1:
+                    self._scan('","', context=_context)
+                    expr = self.expr(_context)
+                    value_list.append(expr)
+                    if self._peek('","', "'\\)'", context=_context) != '","': break
         self._scan("'\\)'", context=_context)
         return value_list
 
diff --git a/test/lib/sql_ast.py b/test/lib/sql_ast.py
index 4430f5a8e50fe271c6da250e01b05e20c4b058ac..8a7538fc90d847823c2f3c98a8eec447bb49a7eb 100644
--- a/test/lib/sql_ast.py
+++ b/test/lib/sql_ast.py
@@ -311,7 +311,9 @@ class StatementCall(StatementSelect):
 
     def __init__(self, proc_name, value_list):
         self.proc_name = proc_name
-        self.value_list= value_list
+# the binary protocol passes everything into procedure as strings
+# convert input to strings to avoid data mangling by the protocol
+        self.value_list = map(lambda val: str(val), value_list)
 
     def pack(self):
         buf = ctypes.create_string_buffer(PACKET_BUF_LEN)